Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions mujoco_warp/_src/collision_driver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,26 +882,27 @@ def test_hfield_maxconpair(self):
np.testing.assert_equal(d.nacon.numpy()[0], 4)

def test_min_friction(self):
_, _, _, d = test_data.fixture(
xml="""
<mujoco>
<worldbody>
<body>
<geom type="sphere" size=".1" friction="0 0 0"/>
<joint type="slide"/>
</body>
<body>
<geom type="sphere" size=".1" friction="0 0 0"/>
<joint type="slide"/>
</body>
</worldbody>
<keyframe>
<key qpos="0 .1"/>
</keyframe>
</mujoco>
""",
keyframe=0,
)
with self.assertWarns(UserWarning):
_, _, _, d = test_data.fixture(
xml="""
<mujoco>
<worldbody>
<body>
<geom type="sphere" size=".1" friction="0 0 0"/>
<joint type="slide"/>
</body>
<body>
<geom type="sphere" size=".1" friction="0 0 0"/>
<joint type="slide"/>
</body>
</worldbody>
<keyframe>
<key qpos="0 .1"/>
</keyframe>
</mujoco>
""",
keyframe=0,
)

self.assertEqual(d.nacon.numpy()[0], 1)
np.testing.assert_allclose(d.contact.friction.numpy()[0], types.MJ_MINMU)
Expand Down
16 changes: 16 additions & 0 deletions mujoco_warp/_src/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# ==============================================================================

import dataclasses
import warnings
from typing import Any, Optional, Sequence, Union

import mujoco
Expand Down Expand Up @@ -153,6 +154,21 @@ def not_implemented(objtype, objid, geomtype):
if not_implemented(objtype, objid, types.GeomType.BOX) and not_implemented(reftype, refid, types.GeomType.BOX):
raise NotImplementedError(f"Collision sensors with box-box collisions are not implemented.")

def _check_friction(name: str, id_: int, condim: int, friction, checks):
for min_condim, indices in checks:
if condim >= min_condim:
for idx in indices:
if friction[idx] < types.MJ_MINMU:
warnings.warn(
f"{name} {id_}: friction[{idx}] ({friction[idx]}) < MJ_MINMU ({types.MJ_MINMU}) with condim={condim} may cause NaN"
)

for geomid in range(mjm.ngeom):
_check_friction("geom", geomid, mjm.geom_condim[geomid], mjm.geom_friction[geomid], [(3, [0]), (4, [1]), (6, [2])])

for pairid in range(mjm.npair):
_check_friction("pair", pairid, mjm.pair_dim[pairid], mjm.pair_friction[pairid], [(3, [0]), (4, [1, 2]), (6, [3, 4])])

# create opt
opt_kwargs = {f.name: getattr(mjm.opt, f.name, None) for f in dataclasses.fields(types.Option)}
if hasattr(mjm.opt, "impratio"):
Expand Down
37 changes: 37 additions & 0 deletions mujoco_warp/_src/io_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,43 @@ def test_contact_sensor_maxmatch(self):

self.assertEqual(m.opt.contact_sensor_maxmatch, 5)

@parameterized.parameters(
'<worldbody><geom type="sphere" size=".1" condim="3" friction="0 0.1 0.1"/></worldbody>',
'<worldbody><geom type="sphere" size=".1" condim="4" friction="1 0 0.1"/></worldbody>',
'<worldbody><geom type="sphere" size=".1" condim="6" friction="1 1 0"/></worldbody>',
"""
<worldbody>
<geom name="g1" type="sphere" size=".1"/>
<geom name="g2" type="sphere" size=".1" pos="0.5 0 0"/>
</worldbody>
<contact>
<pair geom1="g1" geom2="g2" condim="3" friction="0 1 1 1 1"/>
</contact>
""",
"""
<worldbody>
<geom name="g1" type="sphere" size=".1"/>
<geom name="g2" type="sphere" size=".1" pos="0.5 0 0"/>
</worldbody>
<contact>
<pair geom1="g1" geom2="g2" condim="4" friction="1 0 0 1 1"/>
</contact>
""",
"""
<worldbody>
<geom name="g1" type="sphere" size=".1"/>
<geom name="g2" type="sphere" size=".1" pos="0.5 0 0"/>
</worldbody>
<contact>
<pair geom1="g1" geom2="g2" condim="6" friction="1 1 1 0 0"/>
</contact>
""",
)
def test_small_friction_warning(self, xml):
"""Tests that a warning is raised for small friction values."""
with self.assertWarns(UserWarning):
mjwarp.put_model(mujoco.MjModel.from_xml_string(f"<mujoco>{xml}</mujoco>"))

@parameterized.product(active=["true", "false"], make_data=[True, False])
def test_eq_active(self, active, make_data):
mjm, mjd, m, d = test_data.fixture(
Expand Down
Loading