Initial implementation of contact sensor for MJX JAX backend.
PiperOrigin-RevId: 786742643 Change-Id: Idabd98320a4716c597bb596e32a8a90f3cd5f356
This commit is contained in:
committed by
Copybara-Service
parent
5eab59d026
commit
ddb7eb07c7
+3
-2
@@ -243,8 +243,9 @@ The following features are **fully supported** in MJX:
|
|||||||
- ``MAGNETOMETER``, ``CAMPROJECTION``, ``RANGEFINDER``, ``JOINTPOS``, ``TENDONPOS``, ``ACTUATORPOS``, ``BALLQUAT``,
|
- ``MAGNETOMETER``, ``CAMPROJECTION``, ``RANGEFINDER``, ``JOINTPOS``, ``TENDONPOS``, ``ACTUATORPOS``, ``BALLQUAT``,
|
||||||
``FRAMEPOS``, ``FRAMEXAXIS``, ``FRAMEYAXIS``, ``FRAMEZAXIS``, ``FRAMEQUAT``, ``SUBTREECOM``, ``CLOCK``,
|
``FRAMEPOS``, ``FRAMEXAXIS``, ``FRAMEYAXIS``, ``FRAMEZAXIS``, ``FRAMEQUAT``, ``SUBTREECOM``, ``CLOCK``,
|
||||||
``VELOCIMETER``, ``GYRO``, ``JOINTVEL``, ``TENDONVEL``, ``ACTUATORVEL``, ``BALLANGVEL``, ``FRAMELINVEL``,
|
``VELOCIMETER``, ``GYRO``, ``JOINTVEL``, ``TENDONVEL``, ``ACTUATORVEL``, ``BALLANGVEL``, ``FRAMELINVEL``,
|
||||||
``FRAMEANGVEL``, ``SUBTREELINVEL``, ``SUBTREEANGMOM``, ``TOUCH``, ``ACCELEROMETER``, ``FORCE``, ``TORQUE``,
|
``FRAMEANGVEL``, ``SUBTREELINVEL``, ``SUBTREEANGMOM``, ``TOUCH``, ``CONTACT``, ``ACCELEROMETER``, ``FORCE``,
|
||||||
``ACTUATORFRC``, ``JOINTACTFRC``, ``TENDONACTFRC``, ``FRAMELINACC``, ``FRAMEANGACC``
|
``TORQUE``, ``ACTUATORFRC``, ``JOINTACTFRC``, ``TENDONACTFRC``, ``FRAMELINACC``, ``FRAMEANGACC``
|
||||||
|
(``CONTACT``: matching ``none-none``, ``geom-geom``; reduction ``mindist``, ``maxforce``; data ``all``)
|
||||||
* - Lights
|
* - Lights
|
||||||
- Positions and directions of lights
|
- Positions and directions of lights
|
||||||
|
|
||||||
|
|||||||
@@ -432,13 +432,13 @@ def forward(m: Model, d: Data) -> Data:
|
|||||||
d = sensor.sensor_vel(m, d)
|
d = sensor.sensor_vel(m, d)
|
||||||
d = fwd_actuation(m, d)
|
d = fwd_actuation(m, d)
|
||||||
d = fwd_acceleration(m, d)
|
d = fwd_acceleration(m, d)
|
||||||
d = sensor.sensor_acc(m, d)
|
|
||||||
|
|
||||||
if d._impl.efc_J.size == 0:
|
if d._impl.efc_J.size == 0:
|
||||||
d = d.replace(qacc=d.qacc_smooth)
|
d = d.replace(qacc=d.qacc_smooth)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
d = named_scope(solver.solve)(m, d)
|
d = named_scope(solver.solve)(m, d)
|
||||||
|
d = sensor.sensor_acc(m, d)
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
@@ -230,6 +230,41 @@ def _put_model_jax(
|
|||||||
if m.nflex:
|
if m.nflex:
|
||||||
raise NotImplementedError('Flex not implemented for JAX backend.')
|
raise NotImplementedError('Flex not implemented for JAX backend.')
|
||||||
|
|
||||||
|
# contact sensor
|
||||||
|
is_contact_sensor = m.sensor_type == types.SensorType.CONTACT
|
||||||
|
if is_contact_sensor.any():
|
||||||
|
objtype = m.sensor_objtype[is_contact_sensor]
|
||||||
|
reftype = m.sensor_reftype[is_contact_sensor]
|
||||||
|
contact_sensor_type = set(np.concatenate([objtype, reftype]))
|
||||||
|
|
||||||
|
# site filter
|
||||||
|
if types.ObjType.SITE in set(objtype):
|
||||||
|
raise NotImplementedError(
|
||||||
|
'Contact sensor with site matching semantics not implemented for JAX'
|
||||||
|
' backend.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# body semantics
|
||||||
|
if types.ObjType.BODY in contact_sensor_type:
|
||||||
|
raise NotImplementedError(
|
||||||
|
'Contact sensor with body matching semantics not implemented for JAX'
|
||||||
|
' backend.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# subtree semantics
|
||||||
|
if types.ObjType.XBODY in contact_sensor_type:
|
||||||
|
raise NotImplementedError(
|
||||||
|
'Contact sensor with subtree matching semantics not implemented for'
|
||||||
|
' JAX backend.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# net force
|
||||||
|
if (m.sensor_intprm[is_contact_sensor, 1] == 3).any():
|
||||||
|
raise NotImplementedError(
|
||||||
|
'Contact sensor with netforce reduction not implemented for JAX'
|
||||||
|
' backend.'
|
||||||
|
)
|
||||||
|
|
||||||
mesh_geomid = set()
|
mesh_geomid = set()
|
||||||
for g1, g2, ip in collision_driver.geom_pairs(m):
|
for g1, g2, ip in collision_driver.geom_pairs(m):
|
||||||
t1, t2 = m.geom_type[[g1, g2]]
|
t1, t2 = m.geom_type[[g1, g2]]
|
||||||
|
|||||||
@@ -247,6 +247,35 @@ class ModelIOTest(parameterized.TestCase):
|
|||||||
np.array([0, 0, 1, 0, 1, 0, 0]),
|
np.array([0, 0, 1, 0, 1, 0, 0]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@parameterized.parameters(
|
||||||
|
'<contact site="site"/>',
|
||||||
|
'<contact reduce="netforce"/>',
|
||||||
|
'<contact body1="body"/>',
|
||||||
|
'<contact body2="body"/>',
|
||||||
|
'<contact body1="body" body2="body"/>'
|
||||||
|
'<contact subtree1="body"/>',
|
||||||
|
'<contact subtree2="body"/>',
|
||||||
|
'<contact subtree1="body" subtree2="body"/>',
|
||||||
|
)
|
||||||
|
def test_contact_sensor_jax(self, contact_sensor):
|
||||||
|
m = mujoco.MjModel.from_xml_string(f"""
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<site name="site"/>
|
||||||
|
<geom name="plane" type="plane" size="10 10 .001"/>
|
||||||
|
<body name="body">
|
||||||
|
<geom type="sphere" size=".1"/>
|
||||||
|
<joint type="slide" axis="0 0 1"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
<sensor>
|
||||||
|
{contact_sensor}
|
||||||
|
</sensor>
|
||||||
|
</mujoco>
|
||||||
|
""")
|
||||||
|
with self.assertRaises(NotImplementedError):
|
||||||
|
mjx.put_model(m, impl='jax')
|
||||||
|
|
||||||
|
|
||||||
class DataIOTest(parameterized.TestCase):
|
class DataIOTest(parameterized.TestCase):
|
||||||
"""IO tests for mjx.Data."""
|
"""IO tests for mjx.Data."""
|
||||||
|
|||||||
+177
-11
@@ -456,6 +456,25 @@ def sensor_acc(m: Model, d: Data) -> Data:
|
|||||||
}:
|
}:
|
||||||
d = smooth.rne_postconstraint(m, d)
|
d = smooth.rne_postconstraint(m, d)
|
||||||
|
|
||||||
|
contact_intprm = m.sensor_intprm[m.sensor_type == SensorType.CONTACT]
|
||||||
|
contact_maxforce = (contact_intprm[:, 1] == 2).any()
|
||||||
|
contact_dataforce = (contact_intprm[:, 0] & (1 << 1)).any()
|
||||||
|
contact_datatorque = (contact_intprm[:, 0] & (1 << 2)).any()
|
||||||
|
if (m.sensor_type[stage_acc] == SensorType.TOUCH).any() | (
|
||||||
|
(m.sensor_type[stage_acc] == SensorType.CONTACT).any()
|
||||||
|
and (contact_maxforce | contact_dataforce | contact_datatorque)
|
||||||
|
):
|
||||||
|
# compute contact forces
|
||||||
|
contact_force = []
|
||||||
|
condim_ids = []
|
||||||
|
for dim in set(d._impl.contact.dim):
|
||||||
|
force, condim_id = support.contact_force_dim(m, d, dim)
|
||||||
|
contact_force.append(force)
|
||||||
|
condim_ids.append(condim_id)
|
||||||
|
contact_force = jp.concatenate(contact_force)[
|
||||||
|
np.argsort(np.concatenate(condim_ids))
|
||||||
|
]
|
||||||
|
|
||||||
sensors, adrs = [], []
|
sensors, adrs = [], []
|
||||||
|
|
||||||
for sensor_type in sensor_types:
|
for sensor_type in sensor_types:
|
||||||
@@ -466,15 +485,6 @@ def sensor_acc(m: Model, d: Data) -> Data:
|
|||||||
data_type = m.sensor_datatype[idx]
|
data_type = m.sensor_datatype[idx]
|
||||||
|
|
||||||
if sensor_type == SensorType.TOUCH:
|
if sensor_type == SensorType.TOUCH:
|
||||||
# compute contact forces
|
|
||||||
forces = []
|
|
||||||
condim_ids = []
|
|
||||||
for dim in set(d._impl.contact.dim):
|
|
||||||
force, condim_id = support.contact_force_dim(m, d, dim)
|
|
||||||
forces.append(force)
|
|
||||||
condim_ids.append(condim_id)
|
|
||||||
forces = jp.concatenate(forces)[np.argsort(np.concatenate(condim_ids))]
|
|
||||||
|
|
||||||
# get bodies of contact geoms
|
# get bodies of contact geoms
|
||||||
conbody = jp.array(m.geom_bodyid)[d._impl.contact.geom]
|
conbody = jp.array(m.geom_bodyid)[d._impl.contact.geom]
|
||||||
|
|
||||||
@@ -493,7 +503,7 @@ def sensor_acc(m: Model, d: Data) -> Data:
|
|||||||
# compute conray, flip if second body
|
# compute conray, flip if second body
|
||||||
conray = jax.vmap(
|
conray = jax.vmap(
|
||||||
lambda frame, force: math.normalize(frame[0] * force[0])
|
lambda frame, force: math.normalize(frame[0] * force[0])
|
||||||
)(d._impl.contact.frame, forces)
|
)(d._impl.contact.frame, contact_force)
|
||||||
conray = jp.where(conbody1[..., None], -conray, conray)
|
conray = jp.where(conbody1[..., None], -conray, conray)
|
||||||
|
|
||||||
# compute distance, mapping over sites and contacts
|
# compute distance, mapping over sites and contacts
|
||||||
@@ -523,7 +533,163 @@ def sensor_acc(m: Model, d: Data) -> Data:
|
|||||||
dist = jp.vstack(dist)[np.argsort(np.concatenate(dist_id))]
|
dist = jp.vstack(dist)[np.argsort(np.concatenate(dist_id))]
|
||||||
|
|
||||||
# accumulate normal forces for each site
|
# accumulate normal forces for each site
|
||||||
sensor = jp.dot((dist > 0) & contacts, forces[:, 0])
|
sensor = jp.dot((dist > 0) & contacts, contact_force[:, 0])
|
||||||
|
elif sensor_type == SensorType.CONTACT:
|
||||||
|
# maximum number of contacts
|
||||||
|
ncon = d._impl.ncon
|
||||||
|
|
||||||
|
# active contacts
|
||||||
|
dist = d._impl.contact.dist
|
||||||
|
pos = dist - d._impl.contact.includemargin
|
||||||
|
is_contact = pos < 0
|
||||||
|
|
||||||
|
# reduction criteria
|
||||||
|
if contact_maxforce:
|
||||||
|
# compute force magnitude for each contact
|
||||||
|
force_mag = jax.vmap(
|
||||||
|
lambda forcetorque: jp.dot(forcetorque[:3], forcetorque[:3])
|
||||||
|
)(contact_force)
|
||||||
|
|
||||||
|
def _reduce(reduction, mask):
|
||||||
|
if reduction == 1: # mindist
|
||||||
|
return jp.argsort(pos * mask, descending=False)
|
||||||
|
if reduction == 2: # maxforce
|
||||||
|
return jp.argsort(force_mag * mask, descending=True)
|
||||||
|
return jp.arange(mask.size)
|
||||||
|
|
||||||
|
# number of data elements per slot
|
||||||
|
def nslotdata(dataspec):
|
||||||
|
size = 0
|
||||||
|
# found, force, torque, dist, pos, normal, tangent
|
||||||
|
# TODO(taylorhowell): get sizes from mjCONDATA_SIZE
|
||||||
|
for i, size_i in enumerate([1, 3, 3, 1, 3, 3, 3]):
|
||||||
|
if dataspec & (1 << i):
|
||||||
|
size += size_i
|
||||||
|
return size
|
||||||
|
|
||||||
|
dataspecs, reduces = m.sensor_intprm[idx].T
|
||||||
|
dims = m.sensor_dim[idx]
|
||||||
|
objtypes = m.sensor_objtype[idx]
|
||||||
|
refid = m.sensor_refid[idx]
|
||||||
|
reftypes = m.sensor_reftype[idx]
|
||||||
|
|
||||||
|
for dataspec, reduce, objtype, reftype, dim in set(
|
||||||
|
zip(dataspecs, reduces, objtypes, reftypes, dims)
|
||||||
|
):
|
||||||
|
idx_ds = (
|
||||||
|
(dataspec == dataspecs)
|
||||||
|
& (reduce == reduces)
|
||||||
|
& (objtype == objtypes)
|
||||||
|
& (reftype == reftypes)
|
||||||
|
& (dim == dims)
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO(taylorhowell): site filter
|
||||||
|
|
||||||
|
size = nslotdata(dataspec)
|
||||||
|
num = np.minimum(int(dim / size), ncon)
|
||||||
|
|
||||||
|
if objtype == ObjType.UNKNOWN and reftype == ObjType.UNKNOWN:
|
||||||
|
# all contacts match
|
||||||
|
match = np.ones(ncon, dtype=np.bool)
|
||||||
|
|
||||||
|
# matched and reduced contact ids
|
||||||
|
sort = _reduce(reduce, match)
|
||||||
|
cid = sort[:num]
|
||||||
|
|
||||||
|
# number of contacts per sensor
|
||||||
|
nfound = sum(is_contact)
|
||||||
|
|
||||||
|
# if duplicate sensor
|
||||||
|
nsensor = idx_ds.sum()
|
||||||
|
cid = np.tile(cid, (nsensor,))
|
||||||
|
match = np.tile(match[:num], (nsensor,))
|
||||||
|
nfound = np.tile(nfound, (nsensor,))
|
||||||
|
flip = np.ones((cid.size, 3))
|
||||||
|
elif objtype == ObjType.GEOM or reftype == ObjType.GEOM:
|
||||||
|
sensorid1 = objid[idx_ds]
|
||||||
|
sensorid2 = refid[idx_ds]
|
||||||
|
geomid0 = d._impl.contact.geom[:, 0]
|
||||||
|
geomid1 = d._impl.contact.geom[:, 1]
|
||||||
|
|
||||||
|
# match sensor ids and contact geom ids
|
||||||
|
geom0id1 = geomid0 == sensorid1[:, None]
|
||||||
|
geom0id2 = geomid0 == sensorid2[:, None]
|
||||||
|
geom1id1 = geomid1 == sensorid1[:, None]
|
||||||
|
geom1id2 = geomid1 == sensorid2[:, None]
|
||||||
|
|
||||||
|
if objtype == ObjType.GEOM and reftype == ObjType.UNKNOWN: # geom1
|
||||||
|
mask12 = geom0id1
|
||||||
|
mask21 = geom1id1
|
||||||
|
elif objtype == ObjType.UNKNOWN and reftype == ObjType.GEOM: # geom2
|
||||||
|
mask12 = geom0id2
|
||||||
|
mask21 = geom1id2
|
||||||
|
else: # geom1, geom2
|
||||||
|
mask12 = geom0id1 & geom1id2
|
||||||
|
mask21 = geom0id2 & geom1id1
|
||||||
|
|
||||||
|
match = mask12 | mask21
|
||||||
|
|
||||||
|
# matched and reduced contact ids
|
||||||
|
cid = jax.vmap(lambda x: _reduce(reduce, x))(match)[:, :num]
|
||||||
|
cid = cid.reshape(-1)
|
||||||
|
|
||||||
|
# flip direction for force, torque, normal, tangent
|
||||||
|
if reftype == ObjType.UNKNOWN: # geom1
|
||||||
|
is_flip = (geomid1[cid] == np.repeat(sensorid1, num))[:, None]
|
||||||
|
elif objtype == ObjType.UNKNOWN: # geom2
|
||||||
|
is_flip = (geomid0[cid] == np.repeat(sensorid2, num))[:, None]
|
||||||
|
else: # geom1, geom2
|
||||||
|
is_flip = np.repeat(sensorid1 > sensorid2, num)[:, None]
|
||||||
|
|
||||||
|
flip = jp.where(
|
||||||
|
is_flip,
|
||||||
|
jp.array([[1, 1, -1]]),
|
||||||
|
jp.array([[1, 1, 1]]),
|
||||||
|
)
|
||||||
|
|
||||||
|
# number of contacts per sensor
|
||||||
|
nfound = (match * is_contact[None, :]).sum(axis=1)
|
||||||
|
|
||||||
|
match = match[:, :num].reshape(-1)
|
||||||
|
|
||||||
|
# TODO(taylorhowell): matching criteria: body, subtree
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(
|
||||||
|
f'Unsupported contact sensor semantics: {objtype} {reftype}.'
|
||||||
|
)
|
||||||
|
|
||||||
|
slot = []
|
||||||
|
|
||||||
|
if dataspec & (1 << 0): # found
|
||||||
|
slot.append(jp.repeat(nfound, num)[:, None])
|
||||||
|
|
||||||
|
if dataspec & (1 << 1): # force
|
||||||
|
slot.append(flip * contact_force[cid, :3])
|
||||||
|
|
||||||
|
if dataspec & (1 << 2): # torque
|
||||||
|
slot.append(flip * contact_force[cid, 3:])
|
||||||
|
|
||||||
|
if dataspec & (1 << 3): # dist
|
||||||
|
slot.append(dist[cid, None])
|
||||||
|
|
||||||
|
if dataspec & (1 << 4): # pos
|
||||||
|
slot.append(d._impl.contact.pos[cid])
|
||||||
|
|
||||||
|
if dataspec & (1 << 5): # normal
|
||||||
|
slot.append(flip[:, 2, None] * d._impl.contact.frame[cid, 0])
|
||||||
|
|
||||||
|
if dataspec & (1 << 6): # tangent
|
||||||
|
slot.append(flip[:, 2, None] * d._impl.contact.frame[cid, 1])
|
||||||
|
|
||||||
|
found = is_contact[cid] & match
|
||||||
|
sensors.append((found[:, None] * np.hstack(slot)).reshape(-1))
|
||||||
|
adrs.append(
|
||||||
|
(adr[idx_ds][:, None] + np.arange(num * size)[None]).reshape(-1)
|
||||||
|
)
|
||||||
|
continue # avoid adding to sensors/adrs list a second time
|
||||||
|
|
||||||
elif sensor_type == SensorType.ACCELEROMETER:
|
elif sensor_type == SensorType.ACCELEROMETER:
|
||||||
|
|
||||||
@jax.vmap
|
@jax.vmap
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
from absl.testing import absltest
|
from absl.testing import absltest
|
||||||
from absl.testing import parameterized
|
from absl.testing import parameterized
|
||||||
|
import itertools
|
||||||
import jax
|
import jax
|
||||||
from jax import numpy as jp
|
from jax import numpy as jp
|
||||||
import mujoco
|
import mujoco
|
||||||
@@ -97,6 +98,80 @@ class SensorTest(parameterized.TestCase):
|
|||||||
# sensor values
|
# sensor values
|
||||||
_assert_eq(random_sensor, dx.sensordata, 'sensordata')
|
_assert_eq(random_sensor, dx.sensordata, 'sensordata')
|
||||||
|
|
||||||
|
@parameterized.parameters(
|
||||||
|
'type="sphere" size=".1"',
|
||||||
|
'type="sphere" size=".05" margin=".045"',
|
||||||
|
'type="capsule" size=".1 .1" euler="0 89 89"',
|
||||||
|
'type="box" size=".1 .1 .1" euler=".05 .075 .1"',
|
||||||
|
)
|
||||||
|
def test_sensor_contact(self, geom):
|
||||||
|
"""Tests contact sensor."""
|
||||||
|
|
||||||
|
field = ['found', 'force', 'torque', 'dist', 'pos', 'normal', 'tangent']
|
||||||
|
datas = itertools.chain.from_iterable(
|
||||||
|
itertools.combinations(field, i) for i in range(len(field) + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
contact_sensors = ''
|
||||||
|
for num in [1, 2, 3, 4, 5]:
|
||||||
|
for data in datas:
|
||||||
|
data = ' '.join(data)
|
||||||
|
for reduce in ['mindist', 'maxforce']:
|
||||||
|
for match in [
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'geom1="plane"',
|
||||||
|
'geom1="geom1"',
|
||||||
|
'geom1="sphere2"',
|
||||||
|
'geom2="plane"',
|
||||||
|
'geom2="geom1"',
|
||||||
|
'geom2="sphere2"',
|
||||||
|
'geom1="plane" geom2="geom1"',
|
||||||
|
'geom1="geom1" geom2="plane"',
|
||||||
|
'geom1="plane" geom2="sphere2"',
|
||||||
|
'geom1="sphere2" geom2="plane"',
|
||||||
|
'geom1="geom1" geom2="sphere2"',
|
||||||
|
'geom1="sphere2" geom2="geom1"',
|
||||||
|
]:
|
||||||
|
contact_sensors += (
|
||||||
|
f'<contact {match} num="{num}" data="{data}"'
|
||||||
|
f' reduce="{reduce}"/>\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
_MJCF = f"""
|
||||||
|
<mujoco>
|
||||||
|
<compiler angle="degree"/>
|
||||||
|
<worldbody>
|
||||||
|
<geom name="plane" type="plane" size="10 10 .001"/>
|
||||||
|
<body>
|
||||||
|
<geom name="geom1" {geom}/>
|
||||||
|
<joint type="slide" axis="0 0 1"/>
|
||||||
|
</body>
|
||||||
|
<body>
|
||||||
|
<geom name="sphere2" type="sphere" size=".1"/>
|
||||||
|
<joint type="slide" axis="0 0 1"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
<sensor>
|
||||||
|
{contact_sensors}
|
||||||
|
</sensor>
|
||||||
|
<keyframe>
|
||||||
|
<key qpos=".09 1"/>
|
||||||
|
</keyframe>
|
||||||
|
</mujoco>
|
||||||
|
"""
|
||||||
|
m = mujoco.MjModel.from_xml_string(_MJCF)
|
||||||
|
d = mujoco.MjData(m)
|
||||||
|
mujoco.mj_resetDataKeyframe(m, d, 0)
|
||||||
|
|
||||||
|
mx = mjx.put_model(m)
|
||||||
|
dx = mjx.put_data(m, d)
|
||||||
|
|
||||||
|
mujoco.mj_forward(m, d)
|
||||||
|
dx = mjx.forward(mx, dx)
|
||||||
|
|
||||||
|
_assert_eq(dx.sensordata, d.sensordata, 'sensordata')
|
||||||
|
|
||||||
def test_unsupported_sensor(self):
|
def test_unsupported_sensor(self):
|
||||||
"""Tests unsupported sensor raises error."""
|
"""Tests unsupported sensor raises error."""
|
||||||
m = mujoco.MjModel.from_xml_string("""
|
m = mujoco.MjModel.from_xml_string("""
|
||||||
|
|||||||
@@ -380,6 +380,7 @@ class SensorType(enum.IntEnum):
|
|||||||
SUBTREELINVEL: subtree linear velocity
|
SUBTREELINVEL: subtree linear velocity
|
||||||
SUBTREEANGMOM: subtree angular momentum
|
SUBTREEANGMOM: subtree angular momentum
|
||||||
TOUCH: scalar contact normal forces summed over the sensor zone
|
TOUCH: scalar contact normal forces summed over the sensor zone
|
||||||
|
CONTACT: contacts which occurred during the simulation
|
||||||
ACCELEROMETER: accelerometer
|
ACCELEROMETER: accelerometer
|
||||||
FORCE: force
|
FORCE: force
|
||||||
TORQUE: torque
|
TORQUE: torque
|
||||||
@@ -415,6 +416,7 @@ class SensorType(enum.IntEnum):
|
|||||||
SUBTREELINVEL = mujoco.mjtSensor.mjSENS_SUBTREELINVEL
|
SUBTREELINVEL = mujoco.mjtSensor.mjSENS_SUBTREELINVEL
|
||||||
SUBTREEANGMOM = mujoco.mjtSensor.mjSENS_SUBTREEANGMOM
|
SUBTREEANGMOM = mujoco.mjtSensor.mjSENS_SUBTREEANGMOM
|
||||||
TOUCH = mujoco.mjtSensor.mjSENS_TOUCH
|
TOUCH = mujoco.mjtSensor.mjSENS_TOUCH
|
||||||
|
CONTACT = mujoco.mjtSensor.mjSENS_CONTACT
|
||||||
ACCELEROMETER = mujoco.mjtSensor.mjSENS_ACCELEROMETER
|
ACCELEROMETER = mujoco.mjtSensor.mjSENS_ACCELEROMETER
|
||||||
FORCE = mujoco.mjtSensor.mjSENS_FORCE
|
FORCE = mujoco.mjtSensor.mjSENS_FORCE
|
||||||
TORQUE = mujoco.mjtSensor.mjSENS_TORQUE
|
TORQUE = mujoco.mjtSensor.mjSENS_TORQUE
|
||||||
@@ -577,7 +579,6 @@ class ModelC(PyTreeNode):
|
|||||||
flex_bvhnum: jax.Array
|
flex_bvhnum: jax.Array
|
||||||
actuator_plugin: jax.Array
|
actuator_plugin: jax.Array
|
||||||
sensor_plugin: jax.Array
|
sensor_plugin: jax.Array
|
||||||
sensor_intprm: jax.Array
|
|
||||||
plugin: jax.Array
|
plugin: jax.Array
|
||||||
plugin_stateadr: jax.Array
|
plugin_stateadr: jax.Array
|
||||||
|
|
||||||
@@ -843,6 +844,7 @@ class Model(PyTreeNode):
|
|||||||
sensor_objid: np.ndarray
|
sensor_objid: np.ndarray
|
||||||
sensor_reftype: np.ndarray
|
sensor_reftype: np.ndarray
|
||||||
sensor_refid: np.ndarray
|
sensor_refid: np.ndarray
|
||||||
|
sensor_intprm: np.ndarray
|
||||||
sensor_dim: np.ndarray
|
sensor_dim: np.ndarray
|
||||||
sensor_adr: np.ndarray
|
sensor_adr: np.ndarray
|
||||||
sensor_cutoff: np.ndarray
|
sensor_cutoff: np.ndarray
|
||||||
|
|||||||
Reference in New Issue
Block a user