Add mjSpec binding to MJX.
PiperOrigin-RevId: 714026500 Change-Id: I5cb3fe6cc975b3aa43d44185e3bc186b45845cf3
This commit is contained in:
committed by
Copybara-Service
parent
6f7fba794b
commit
3f32cc2479
@@ -13,7 +13,8 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
"""Engine support functions."""
|
"""Engine support functions."""
|
||||||
from typing import Optional, Tuple, Union
|
from collections.abc import Sequence
|
||||||
|
from typing import Optional, Tuple, Union, Any
|
||||||
|
|
||||||
import jax
|
import jax
|
||||||
from jax import numpy as jp
|
from jax import numpy as jp
|
||||||
@@ -236,7 +237,7 @@ def _getadr(
|
|||||||
def id2name(
|
def id2name(
|
||||||
m: Union[Model, mujoco.MjModel], typ: mujoco._enums.mjtObj, i: int
|
m: Union[Model, mujoco.MjModel], typ: mujoco._enums.mjtObj, i: int
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""Gets the name of an object with the specified mjtObj type and id.
|
"""Gets the name of an object with the specified mjtObj type and ids.
|
||||||
|
|
||||||
See mujoco.id2name for more info.
|
See mujoco.id2name for more info.
|
||||||
|
|
||||||
@@ -284,6 +285,174 @@ def name2id(
|
|||||||
return names_map.get(name, -1)
|
return names_map.get(name, -1)
|
||||||
|
|
||||||
|
|
||||||
|
class BindModel(object):
|
||||||
|
"""Class holding the requested MJX Model and spec id for binding a spec to Model."""
|
||||||
|
|
||||||
|
def __init__(self, model: Model, specs: Sequence[Any]):
|
||||||
|
self.model = model
|
||||||
|
try:
|
||||||
|
iter(specs)
|
||||||
|
except TypeError:
|
||||||
|
specs = [specs]
|
||||||
|
ids = []
|
||||||
|
for spec in specs:
|
||||||
|
match spec:
|
||||||
|
case mujoco.MjsBody():
|
||||||
|
self.prefix = 'body_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_BODY, spec.name))
|
||||||
|
case mujoco.MjsJoint():
|
||||||
|
self.prefix = 'jnt_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_JOINT, spec.name))
|
||||||
|
case mujoco.MjsGeom():
|
||||||
|
self.prefix = 'geom_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_GEOM, spec.name))
|
||||||
|
case mujoco.MjsSite():
|
||||||
|
self.prefix = 'site_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_SITE, spec.name))
|
||||||
|
case mujoco.MjsLight():
|
||||||
|
self.prefix = 'light_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_LIGHT, spec.name))
|
||||||
|
case mujoco.MjsCamera():
|
||||||
|
self.prefix = 'cam_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_CAMERA, spec.name))
|
||||||
|
case mujoco.MjsMesh():
|
||||||
|
self.prefix = 'mesh_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_MESH, spec.name))
|
||||||
|
case mujoco.MjsHfield():
|
||||||
|
self.prefix = 'hfield_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_HFIELD, spec.name))
|
||||||
|
case mujoco.MjsPair():
|
||||||
|
self.prefix = 'pair_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_PAIR, spec.name))
|
||||||
|
case mujoco.MjsTendon():
|
||||||
|
self.prefix = 'tendon_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_TENDON, spec.name))
|
||||||
|
case mujoco.MjsActuator():
|
||||||
|
self.prefix = 'actuator_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_ACTUATOR, spec.name))
|
||||||
|
case mujoco.MjsSensor():
|
||||||
|
self.prefix = 'sensor_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_SENSOR, spec.name))
|
||||||
|
case mujoco.MjsNumeric():
|
||||||
|
self.prefix = 'numeric_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_NUMERIC, spec.name))
|
||||||
|
case mujoco.MjsText():
|
||||||
|
self.prefix = 'text_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_TEXT, spec.name))
|
||||||
|
case mujoco.MjsTuple():
|
||||||
|
self.prefix = 'tuple_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_TUPLE, spec.name))
|
||||||
|
case mujoco.MjsKey():
|
||||||
|
self.prefix = 'key_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_KEY, spec.name))
|
||||||
|
case mujoco.MjsEquality():
|
||||||
|
self.prefix = 'eq_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_EQUALITY, spec.name))
|
||||||
|
case mujoco.MjsExclude():
|
||||||
|
self.prefix = 'exclude_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_EXCLUDE, spec.name))
|
||||||
|
case mujoco.MjsSkin():
|
||||||
|
self.prefix = 'skin_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_SKIN, spec.name))
|
||||||
|
case mujoco.MjsMaterial():
|
||||||
|
self.prefix = 'material_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_MATERIAL, spec.name))
|
||||||
|
case _:
|
||||||
|
raise ValueError('invalid spec type')
|
||||||
|
if len(ids) == 1:
|
||||||
|
self.id = ids[0]
|
||||||
|
else:
|
||||||
|
self.id = ids
|
||||||
|
|
||||||
|
def __getattr__(self, name: str):
|
||||||
|
return getattr(self.model, self.prefix + name)[self.id, :]
|
||||||
|
|
||||||
|
|
||||||
|
def _bind_model(self: Model, obj: Sequence[Any]) -> BindModel:
|
||||||
|
"""Bind a Mujoco spec to an MJX Model."""
|
||||||
|
return BindModel(self, obj)
|
||||||
|
|
||||||
|
|
||||||
|
class BindData(object):
|
||||||
|
"""Class holding the requested MJX Data and spec id for binding a spec to Data."""
|
||||||
|
|
||||||
|
def __init__(self, data: Data, model: Model, specs: Sequence[Any]):
|
||||||
|
self.data = data
|
||||||
|
try:
|
||||||
|
iter(specs)
|
||||||
|
except TypeError:
|
||||||
|
specs = [specs]
|
||||||
|
ids = []
|
||||||
|
for spec in specs:
|
||||||
|
match spec:
|
||||||
|
case mujoco.MjsBody():
|
||||||
|
self.prefix = ''
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_BODY, spec.name))
|
||||||
|
case mujoco.MjsJoint():
|
||||||
|
self.prefix = 'jnt_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_JOINT, spec.name))
|
||||||
|
case mujoco.MjsGeom():
|
||||||
|
self.prefix = 'geom_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_GEOM, spec.name))
|
||||||
|
case mujoco.MjsSite():
|
||||||
|
self.prefix = 'site_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_SITE, spec.name))
|
||||||
|
case mujoco.MjsLight():
|
||||||
|
self.prefix = 'light_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_LIGHT, spec.name))
|
||||||
|
case mujoco.MjsCamera():
|
||||||
|
self.prefix = 'cam_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_CAMERA, spec.name))
|
||||||
|
case mujoco.MjsTendon():
|
||||||
|
self.prefix = 'tendon_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_TENDON, spec.name))
|
||||||
|
case mujoco.MjsActuator():
|
||||||
|
self.prefix = 'actuator_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_ACTUATOR, spec.name))
|
||||||
|
case mujoco.MjsSensor():
|
||||||
|
self.prefix = 'sensor_'
|
||||||
|
ids.append(name2id(model, mujoco.mjtObj.mjOBJ_SENSOR, spec.name))
|
||||||
|
case _:
|
||||||
|
raise ValueError('invalid spec type')
|
||||||
|
if len(ids) == 1:
|
||||||
|
self.id = ids[0]
|
||||||
|
else:
|
||||||
|
self.id = ids
|
||||||
|
|
||||||
|
def __getname(self, name: str):
|
||||||
|
try:
|
||||||
|
getattr(self.data, self.prefix + name)
|
||||||
|
return self.prefix + name
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
getattr(self.data, name)
|
||||||
|
return name
|
||||||
|
except AttributeError as e:
|
||||||
|
raise ValueError(f'invalid name: {name}') from e
|
||||||
|
|
||||||
|
def __getattr__(self, name: str):
|
||||||
|
return getattr(self.data, self.__getname(name))[self.id, ...]
|
||||||
|
|
||||||
|
def set(self, name: str, value: jax.Array) -> Data:
|
||||||
|
"""Set the value of an array in an MJX Data."""
|
||||||
|
array = getattr(self.data, self.__getname(name))
|
||||||
|
if len(value) == 1:
|
||||||
|
array = array.at[self.id].set(value[0])
|
||||||
|
else:
|
||||||
|
for i, v in enumerate(value):
|
||||||
|
array = array.at[self.id[i]].set(v)
|
||||||
|
return self.data.replace(**{self.__getname(name): array})
|
||||||
|
|
||||||
|
|
||||||
|
def _bind_data(self: Data, model: Model, obj: Sequence[Any]) -> BindData:
|
||||||
|
"""Bind a Mujoco spec to an MJX Data."""
|
||||||
|
return BindData(self, model, obj)
|
||||||
|
|
||||||
|
|
||||||
|
Model.bind = _bind_model
|
||||||
|
Data.bind = _bind_data
|
||||||
|
|
||||||
|
|
||||||
def _decode_pyramid(
|
def _decode_pyramid(
|
||||||
pyramid: jax.Array, mu: jax.Array, condim: int
|
pyramid: jax.Array, mu: jax.Array, condim: int
|
||||||
) -> jax.Array:
|
) -> jax.Array:
|
||||||
|
|||||||
@@ -157,6 +157,92 @@ class SupportTest(parameterized.TestCase):
|
|||||||
i = i if n is not None else -1
|
i = i if n is not None else -1
|
||||||
self.assertEqual(support.name2id(mx, obj, n), i)
|
self.assertEqual(support.name2id(mx, obj, n), i)
|
||||||
|
|
||||||
|
def test_bind(self):
|
||||||
|
xml = """
|
||||||
|
<mujoco model="test_bind_model">
|
||||||
|
<worldbody>
|
||||||
|
<body pos="1 2 3" name="body1">
|
||||||
|
<joint axis="1 0 0" type="slide" name="joint1"/>
|
||||||
|
<geom size="1 2 3" type="box" name="geom1"/>
|
||||||
|
</body>
|
||||||
|
<body pos="4 5 6" name="body2">
|
||||||
|
<joint axis="0 1 0" type="slide" name="joint2"/>
|
||||||
|
<geom size="4 5 6" type="box" name="geom2"/>
|
||||||
|
</body>
|
||||||
|
<body pos="7 8 9" name="body3">
|
||||||
|
<joint axis="0 0 1" type="slide" name="joint3"/>
|
||||||
|
<geom size="7 8 9" type="box" name="geom3"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
|
||||||
|
<actuator>
|
||||||
|
<motor name="actuator1" joint="joint1"/>
|
||||||
|
<motor name="actuator2" joint="joint2"/>
|
||||||
|
<motor name="actuator3" joint="joint3"/>
|
||||||
|
</actuator>
|
||||||
|
</mujoco>
|
||||||
|
"""
|
||||||
|
|
||||||
|
s = mujoco.MjSpec.from_string(xml)
|
||||||
|
m = s.compile()
|
||||||
|
d = mujoco.MjData(m)
|
||||||
|
mx = mjx.put_model(m)
|
||||||
|
dx = mjx.put_data(m, d)
|
||||||
|
mujoco.mj_step(m, d)
|
||||||
|
dx = mjx.step(mx, dx)
|
||||||
|
|
||||||
|
# test getting
|
||||||
|
np.testing.assert_array_equal(mx.bind(s.bodies).pos, m.body_pos)
|
||||||
|
np.testing.assert_array_equal(dx.bind(mx, s.bodies).xpos, d.xpos)
|
||||||
|
for i in range(m.nbody):
|
||||||
|
np.testing.assert_array_equal(m.bind(s.bodies[i]).pos, m.body_pos[i, :])
|
||||||
|
np.testing.assert_array_equal(mx.bind(s.bodies[i]).pos, m.body_pos[i, :])
|
||||||
|
np.testing.assert_array_equal(d.bind(s.bodies[i]).xpos, d.xpos[i, :])
|
||||||
|
np.testing.assert_array_equal(
|
||||||
|
dx.bind(mx, s.bodies[i]).xpos, d.xpos[i, :]
|
||||||
|
)
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(mx.bind(s.geoms).size, m.geom_size)
|
||||||
|
np.testing.assert_array_equal(dx.bind(mx, s.geoms).xpos, d.geom_xpos)
|
||||||
|
for i in range(m.ngeom):
|
||||||
|
np.testing.assert_array_equal(m.bind(s.geoms[i]).size, m.geom_size[i, :])
|
||||||
|
np.testing.assert_array_equal(mx.bind(s.geoms[i]).size, m.geom_size[i, :])
|
||||||
|
np.testing.assert_array_equal(d.bind(s.geoms[i]).xpos, d.geom_xpos[i, :])
|
||||||
|
np.testing.assert_array_equal(
|
||||||
|
dx.bind(mx, s.geoms[i]).xpos, d.geom_xpos[i, :]
|
||||||
|
)
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(mx.bind(s.joints).axis, m.jnt_axis)
|
||||||
|
for i in range(m.njnt):
|
||||||
|
np.testing.assert_array_equal(m.bind(s.joints[i]).axis, m.jnt_axis[i, :])
|
||||||
|
np.testing.assert_array_equal(mx.bind(s.joints[i]).axis, m.jnt_axis[i, :])
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, d.ctrl)
|
||||||
|
for i in range(m.nu):
|
||||||
|
np.testing.assert_array_equal(d.bind(s.actuators[i]).ctrl, d.ctrl[i])
|
||||||
|
np.testing.assert_array_equal(
|
||||||
|
dx.bind(mx, s.actuators[i]).ctrl, d.ctrl[i]
|
||||||
|
)
|
||||||
|
|
||||||
|
# test setting
|
||||||
|
np.testing.assert_array_equal(d.ctrl, [0, 0, 0])
|
||||||
|
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, d.ctrl)
|
||||||
|
dx2 = dx.bind(mx, s.actuators).set('ctrl', [1, 2, 3])
|
||||||
|
np.testing.assert_array_equal(dx2.bind(mx, s.actuators).ctrl, [1, 2, 3])
|
||||||
|
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, [0, 0, 0])
|
||||||
|
dx3 = dx.bind(mx, s.actuators[1:]).set('ctrl', [4, 5])
|
||||||
|
np.testing.assert_array_equal(dx3.bind(mx, s.actuators).ctrl, [0, 4, 5])
|
||||||
|
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, [0, 0, 0])
|
||||||
|
dx4 = dx.bind(mx, s.actuators[1]).set('ctrl', [6])
|
||||||
|
np.testing.assert_array_equal(dx4.bind(mx, s.actuators).ctrl, [0, 6, 0])
|
||||||
|
np.testing.assert_array_equal(dx.bind(mx, s.actuators).ctrl, [0, 0, 0])
|
||||||
|
|
||||||
|
# test invalid name
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
print(dx.bind(mx, s.actuators).actuator_ctrl)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
print(dx.bind(mx, s.actuators).set('actuator_ctrl', [1, 2, 3]))
|
||||||
|
|
||||||
_CONTACTS = """
|
_CONTACTS = """
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user