Rename backend_impl to impl. Remove _full_compat from callsites.

PiperOrigin-RevId: 770400560
Change-Id: I7bec79aeae07322a30b15cc04d4dd05077741a8b
This commit is contained in:
Baruch Tabanpour
2025-06-11 18:04:51 -07:00
committed by Copybara-Service
parent f978b4eea3
commit 65deedbc75
4 changed files with 217 additions and 217 deletions
+93 -93
View File
@@ -41,51 +41,51 @@ def _is_cuda_gpu_device(device: jax.Device) -> bool:
return device in cuda_devices
def _resolve_backend_impl(
def _resolve_impl(
device: jax.Device,
) -> types.BackendImpl:
"""Pick a default backend impl based on the device specified."""
) -> types.Impl:
"""Pick a default implementation based on the device specified."""
if _is_cuda_gpu_device(device):
# TODO(btaba): Remove flag once Warp is ready to launch.
mjx_warp_enabled = os.environ.get('MJX_WARP_ENABLED', 'f').lower() == 'true'
if mjx_warp_enabled:
logging.debug('Picking default backend implementation: Warp.')
return types.BackendImpl.WARP
logging.debug('Picking default implementation: Warp.')
return types.Impl.WARP
logging.info('MJX Warp is disabled via MJX_WARP_ENABLED=false.')
if device.platform in ('gpu', 'tpu'):
logging.debug('Picking default backend implementation: JAX.')
return types.BackendImpl.JAX
logging.debug('Picking default implementation: JAX.')
return types.Impl.JAX
if device.platform == 'cpu':
mjx_c_default = (
os.environ.get('MJX_C_DEFAULT_ENABLED', 'f').lower() == 'true'
)
if mjx_c_default:
logging.debug('Picking default backend implementation: C.')
return types.BackendImpl.C
return types.BackendImpl.JAX
logging.debug('Picking default implementation: C.')
return types.Impl.C
return types.Impl.JAX
raise ValueError(f'Unsupported device: {device}')
def _resolve_device(
backend_impl: types.BackendImpl,
impl: types.Impl,
) -> jax.Device:
"""Resolves a device based on the backend implementation."""
backend_impl = types.BackendImpl(backend_impl)
if backend_impl == types.BackendImpl.JAX:
"""Resolves a device based on the implementation."""
impl = types.Impl(impl)
if impl == types.Impl.JAX:
device_0 = jax.devices()[0]
logging.debug('Picking default device: %s.', device_0)
return device_0
if backend_impl == types.BackendImpl.C:
if impl == types.Impl.C:
cpu_0 = jax.devices('cpu')[0]
logging.debug('Picking default device: %s', cpu_0)
return cpu_0
if backend_impl == types.BackendImpl.WARP:
# WARP backend requires a CUDA GPU.
if impl == types.Impl.WARP:
# WARP implementation requires a CUDA GPU.
cuda_gpus = [d for d in jax.devices('cuda')]
if not cuda_gpus:
raise AssertionError(
@@ -96,64 +96,64 @@ def _resolve_device(
logging.debug('Picking default device: %s', cuda_gpus[0])
return cuda_gpus[0]
raise ValueError(f'Unsupported backend implementation: {backend_impl}')
raise ValueError(f'Unsupported implementation: {impl}')
def _check_backend_impl_device_compatibility(
backend_impl: Union[str, types.BackendImpl],
def _check_impl_device_compatibility(
impl: Union[str, types.Impl],
device: jax.Device,
) -> None:
"""Checks that the backend implementation is compatible with the device."""
if backend_impl is None:
raise ValueError('No backend implementation specified.')
"""Checks that the implementation is compatible with the device."""
if impl is None:
raise ValueError('No implementation specified.')
backend_impl = types.BackendImpl(backend_impl)
impl = types.Impl(impl)
if backend_impl == types.BackendImpl.WARP:
if impl == types.Impl.WARP:
if not _is_cuda_gpu_device(device):
raise AssertionError(
'Warp backend implementation requires a CUDA GPU device, got '
'Warp implementation requires a CUDA GPU device, got '
f'{device}.'
)
mjx_warp_enabled = os.environ.get('MJX_WARP_ENABLED', 'f').lower() == 'true'
if not mjx_warp_enabled:
raise AssertionError(
'Warp backend implementation is disabled via MJX_WARP_ENABLED=false.'
'Warp implementation is disabled via MJX_WARP_ENABLED=false.'
)
is_cpu_device = device.platform == 'cpu'
if backend_impl == types.BackendImpl.C:
if impl == types.Impl.C:
if not is_cpu_device:
raise AssertionError(
f'C backend implementation requires a CPU device, got {device}.'
f'C implementation requires a CPU device, got {device}.'
)
# NB: JAX backend works with any device.
# NB: JAX implementation works with any device.
def _resolve_backend_impl_and_device(
backend_impl: Optional[Union[str, types.BackendImpl]],
def _resolve_impl_and_device(
impl: Optional[Union[str, types.Impl]],
device: Optional[jax.Device] = None,
) -> Tuple[types.BackendImpl, jax.Device]:
"""Resolves a backend implementation and device."""
if backend_impl:
backend_impl = types.BackendImpl(backend_impl)
) -> Tuple[types.Impl, jax.Device]:
"""Resolves a implementation and device."""
if impl:
impl = types.Impl(impl)
has_backend_impl, has_device = backend_impl is not None, device is not None
if (has_backend_impl, has_device) == (True, True):
has_impl, has_device = impl is not None, device is not None
if (has_impl, has_device) == (True, True):
pass
elif (has_backend_impl, has_device) == (True, False):
device = _resolve_device(backend_impl)
elif (has_backend_impl, has_device) == (False, True):
backend_impl = _resolve_backend_impl(device)
elif (has_impl, has_device) == (True, False):
device = _resolve_device(impl)
elif (has_impl, has_device) == (False, True):
impl = _resolve_impl(device)
else:
device = jax.devices(jax.default_backend())[0]
logging.info('Using JAX default device: %s.', device)
backend_impl = _resolve_backend_impl(device)
impl = _resolve_impl(device)
_check_backend_impl_device_compatibility(backend_impl, device)
return backend_impl, device # pytype: disable=bad-return-type
_check_impl_device_compatibility(impl, device)
return impl, device # pytype: disable=bad-return-type
def _strip_weak_type(tree):
@@ -167,7 +167,7 @@ def _strip_weak_type(tree):
def _put_option(
o: mujoco.MjOption,
backend_impl: types.BackendImpl,
impl: types.Impl,
impl_fields: Optional[dict[str, Any]] = None,
) -> types.Option:
"""Returns mjx.Option given mujoco.MjOption."""
@@ -195,7 +195,7 @@ def _put_option(
fields['disableflags'] = types.DisableBit(o.disableflags)
fields['enableflags'] = types.EnableBit(o.enableflags)
if backend_impl == types.BackendImpl.JAX:
if impl == types.Impl.JAX:
has_fluid_params = o.density > 0 or o.viscosity > 0 or o.wind.any()
implicitfast = o.integrator == mujoco.mjtIntegrator.mjINT_IMPLICITFAST
if implicitfast and has_fluid_params:
@@ -203,12 +203,12 @@ def _put_option(
fields['has_fluid_params'] = has_fluid_params
return types.OptionJAX(**fields, **(impl_fields or {}))
if backend_impl == types.BackendImpl.C:
if impl == types.Impl.C:
c_field_keys = types.OptionC.__annotations__.keys() - fields.keys()
c_fields = {k: getattr(o, k, None) for k in c_field_keys}
return types.OptionC(**fields, **c_fields, **(impl_fields or {}))
raise NotImplementedError(f'Unsupported backend: {backend_impl}')
raise NotImplementedError(f'Unsupported implementation: {impl}')
def _put_statistic(s: mujoco.MjStatistic) -> types.Statistic:
@@ -283,7 +283,7 @@ def _put_model_jax(
mj_field_names = {f.name for f in types.Model.fields() if f.name != '_impl'}
fields = {f: getattr(m, f) for f in mj_field_names}
fields['cam_mat0'] = fields['cam_mat0'].reshape((-1, 3, 3))
fields['opt'] = _put_option(m.opt, types.BackendImpl.JAX)
fields['opt'] = _put_option(m.opt, types.Impl.JAX)
fields['stat'] = _put_statistic(m.stat)
fields_jax = {}
@@ -340,7 +340,7 @@ def _put_model_c(
mj_field_names = {f.name for f in types.Model.fields() if f.name != '_impl'}
fields = {f: getattr(m, f) for f in mj_field_names}
fields['cam_mat0'] = fields['cam_mat0'].reshape((-1, 3, 3))
fields['opt'] = _put_option(m.opt, backend_impl=types.BackendImpl.C)
fields['opt'] = _put_option(m.opt, impl=types.Impl.C)
fields['stat'] = _put_statistic(m.stat)
c_impl_keys = (
@@ -359,7 +359,7 @@ def _put_model_c(
def put_model(
m: mujoco.MjModel,
device: Optional[jax.Device] = None,
backend_impl: Optional[Union[str, types.BackendImpl]] = None,
impl: Optional[Union[str, types.Impl]] = None,
_full_compat: bool = False, # pylint: disable=invalid-name
) -> types.Model:
"""Puts mujoco.MjModel onto a device, resulting in mjx.Model.
@@ -367,7 +367,7 @@ def put_model(
Args:
m: the model to put onto device
device: which device to use - if unspecified picks the default device
backend_impl: backend implementation to use
impl: implementation to use
_full_compat: put all MjModel fields onto device irrespective of MJX support
This is an experimental feature. Avoid using it for now.
@@ -375,28 +375,29 @@ def put_model(
an mjx.Model placed on device
Raises:
ValueError: if backend_impl is not supported
ValueError: if impl is not supported
DeprecationWarning: if _full_compat is True
"""
if _full_compat:
warnings.warn(
'mjx.put_model(..., _full_compat=True) is deprecated. Use'
' mjx.put_model(..., backend_impl=types.BackendImpl.C) instead.',
'mjx.put_model(..., _full_compat=True) is deprecated and will be'
' removed in MuJoCo >=3.4. Use mjx.put_model(..., impl=types.Impl.C)'
' instead.',
DeprecationWarning,
stacklevel=2,
)
backend_impl = types.BackendImpl.C
impl = types.Impl.C
backend_impl, device = _resolve_backend_impl_and_device(backend_impl, device)
if backend_impl == types.BackendImpl.JAX:
impl, device = _resolve_impl_and_device(impl, device)
if impl == types.Impl.JAX:
return _put_model_jax(m, device)
elif backend_impl == types.BackendImpl.C:
elif impl == types.Impl.C:
return _put_model_c(m, device)
elif backend_impl == types.BackendImpl.WARP:
raise NotImplementedError('Warp backend not implemented yet.')
elif impl == types.Impl.WARP:
raise NotImplementedError('Warp implementation not implemented yet.')
else:
raise ValueError(f'Unsupported backend implementation: {backend_impl}')
raise ValueError(f'Unsupported implementation: {impl}')
def _make_data_public_fields(m: types.Model) -> Dict[str, Any]:
@@ -696,7 +697,7 @@ def _make_data_c(
def make_data(
m: Union[types.Model, mujoco.MjModel],
device: Optional[jax.Device] = None,
backend_impl: Optional[Union[str, types.BackendImpl]] = None,
impl: Optional[Union[str, types.Impl]] = None,
_full_compat: bool = False, # pylint: disable=invalid-name
) -> types.Data:
"""Allocate and initialize Data.
@@ -704,7 +705,7 @@ def make_data(
Args:
m: the model to use
device: which device to use - if unspecified picks the default device
backend_impl: backend implementation to use
impl: implementation to use ('jax', 'warp')
_full_compat: put all fields onto device irrespective of MJX support This is
an experimental feature. Avoid using it for now. If using this flag, also
use _full_compat for put_model.
@@ -713,35 +714,34 @@ def make_data(
an initialized mjx.Data placed on device
Raises:
ValueError: if the model's backend_impl does not match the make_data
backend_impl
NotImplementedError: if the backend_impl is not implemented yet
ValueError: if the model's impl does not match the make_data impl
NotImplementedError: if the impl is not implemented yet
DeprecationWarning: if _full_compat is used
"""
if _full_compat:
warnings.warn(
'mjx.make_data(..., _full_compat=True) is deprecated. Use'
' mjx.make_data(..., backend_impl=types.BackendImpl.C) instead.',
' mjx.make_data(..., impl=types.Impl.C) instead.',
DeprecationWarning,
stacklevel=2,
)
backend_impl = types.BackendImpl.C
impl = types.Impl.C
backend_impl, device = _resolve_backend_impl_and_device(backend_impl, device)
impl, device = _resolve_impl_and_device(impl, device)
if isinstance(m, types.Model) and m.backend_impl != backend_impl:
if isinstance(m, types.Model) and m.impl != impl:
raise ValueError(
f'Model backend_impl {m.backend_impl} does not match make_data '
f'backend_impl {backend_impl}.'
f'Model impl {m.impl} does not match make_data '
f'implementation {impl}.'
)
if backend_impl == types.BackendImpl.JAX:
if impl == types.Impl.JAX:
return _make_data_jax(m, device)
elif backend_impl == types.BackendImpl.C:
elif impl == types.Impl.C:
return _make_data_c(m, device)
raise NotImplementedError(
f'make_data for backend_impl "{backend_impl}" not implemented yet.'
f'make_data for implementation "{impl}" not implemented yet.'
)
@@ -951,7 +951,7 @@ def _put_data_c(
if hasattr(d, f.name)
}
# TODO(stunya): support islanding via C backend impl.
# TODO(stunya): support islanding via C impl.
impl_fields['solver_niter'] = impl_fields['solver_niter'][0]
# TODO(btaba): remove dense actuator moment.
@@ -1039,7 +1039,7 @@ def put_data(
m: mujoco.MjModel,
d: mujoco.MjData,
device: Optional[jax.Device] = None,
backend_impl: Optional[Union[str, types.BackendImpl]] = None,
impl: Optional[Union[str, types.Impl]] = None,
_full_compat: bool = False, # pylint: disable=invalid-name
) -> types.Data:
"""Puts mujoco.MjData onto a device, resulting in mjx.Data.
@@ -1048,7 +1048,7 @@ def put_data(
m: the model to use
d: the data to put on device
device: which device to use - if unspecified picks the default device
backend_impl: backend implementation to use
impl: implementation to use ('jax', 'warp')
_full_compat: put all MjModel fields onto device irrespective of MJX support
This is an experimental feature. Avoid using it for now. If using this
flag, also use _full_compat for put_model.
@@ -1059,20 +1059,20 @@ def put_data(
if _full_compat:
warnings.warn(
'mjx.put_data(..., _full_compat=True) is deprecated. Use'
' mjx.put_data(..., backend_impl=types.BackendImpl.C) instead.',
' mjx.put_data(..., impl=types.Impl.C) instead.',
DeprecationWarning,
stacklevel=2,
)
backend_impl = types.BackendImpl.C
impl = types.Impl.C
backend_impl, device = _resolve_backend_impl_and_device(backend_impl, device)
if backend_impl == types.BackendImpl.JAX:
impl, device = _resolve_impl_and_device(impl, device)
if impl == types.Impl.JAX:
return _put_data_jax(m, d, device)
elif backend_impl == types.BackendImpl.C:
elif impl == types.Impl.C:
return _put_data_c(m, d, device)
raise NotImplementedError(
f'put_data for backend_impl "{backend_impl}" not implemented yet.'
f'put_data for implementation "{impl}" not implemented yet.'
)
@@ -1097,7 +1097,7 @@ def _get_data_into(
batch_size = d.qpos.shape[0] if batched else 1
dof_i, dof_j = [], []
if d.backend_impl == types.BackendImpl.JAX:
if d.impl == types.Impl.JAX:
for i in range(m.nv):
j = i
while j > -1:
@@ -1116,13 +1116,13 @@ def _get_data_into(
if ncon != result_i.ncon or nefc != result_i.nefc or nj != result_i.nJ:
mujoco._functions._realloc_con_efc(result_i, ncon=ncon, nefc=nefc, nJ=nj) # pylint: disable=protected-access
if d.backend_impl == types.BackendImpl.JAX:
if d.impl == types.Impl.JAX:
all_fields = types.Data.fields() + types.DataJAX.fields()
elif d.backend_impl == types.BackendImpl.C:
elif d.impl == types.Impl.C:
all_fields = types.Data.fields() + types.DataC.fields()
else:
raise NotImplementedError(
f'get_data_into for backend_impl "{d.backend_impl}" not implemented'
f'get_data_into for implementation "{d.impl}" not implemented'
' yet.'
)
@@ -1188,7 +1188,7 @@ def _get_data_into(
value = value.reshape(-1)
elif field.name.startswith('efc_'):
value = value[efc_active]
if d.backend_impl == types.BackendImpl.JAX:
if d.impl == types.Impl.JAX:
if field.name == 'qM' and not support.is_sparse(m):
value = value[dof_i, dof_j]
elif field.name == 'qLD' and not support.is_sparse(m):
@@ -1226,12 +1226,12 @@ def get_data_into(
d = jax.device_get(d)
if d.backend_impl in (types.BackendImpl.JAX, types.BackendImpl.C):
if d.impl in (types.Impl.JAX, types.Impl.C):
# TODO(stunya): Split out _get_data_into once codepaths diverge enough.
return _get_data_into(result, m, d)
raise NotImplementedError(
f'get_data_into for backend_impl "{d.backend_impl}" not implemented yet.'
f'get_data_into for implementation "{d.impl}" not implemented yet.'
)
+114 -114
View File
@@ -26,8 +26,8 @@ from mujoco.mjx._src import io as mjx_io
from mujoco.mjx._src import test_util
# pylint: disable=g-importing-member
from mujoco.mjx._src.types import BackendImpl
from mujoco.mjx._src.types import ConeType
from mujoco.mjx._src.types import Impl
# pylint: enable=g-importing-member
import numpy as np
@@ -114,11 +114,11 @@ class ModelIOTest(parameterized.TestCase):
@parameterized.product(
xml=(_MULTIPLE_CONVEX_OBJECTS, _MULTIPLE_CONSTRAINTS),
backend_impl=('jax', 'c'),
impl=('jax', 'c'),
)
def test_put_model(self, xml, backend_impl):
def test_put_model(self, xml, impl):
m = mujoco.MjModel.from_xml_string(xml)
mx = mjx.put_model(m, backend_impl=backend_impl)
mx = mjx.put_model(m, impl=impl)
def assert_not_weak_type(x):
if isinstance(x, jax.Array):
@@ -140,10 +140,10 @@ class ModelIOTest(parameterized.TestCase):
self.assertEqual(mx.nM, m.nM)
self.assertAlmostEqual(mx.opt.timestep, m.opt.timestep)
if backend_impl == 'jax':
if impl == 'jax':
# fields restricted to MuJoCo should not be populated
self.assertFalse(hasattr(mx, 'bvh_aabb'))
elif backend_impl == 'c':
elif impl == 'c':
# Options specific to C are populated.
self.assertEqual(mx.opt.apirate, m.opt.apirate)
# Fields private to C backend impl are populated.
@@ -177,7 +177,7 @@ class ModelIOTest(parameterized.TestCase):
mujoco.MjModel.from_xml_string(
'<mujoco><option viscosity="3.0"/><worldbody/></mujoco>'
),
backend_impl='jax',
impl='jax',
)
self.assertTrue(m.opt.has_fluid_params)
@@ -218,7 +218,7 @@ class ModelIOTest(parameterized.TestCase):
</body>
</worldbody>
</mujoco>"""),
backend_impl='jax',
impl='jax',
)
def test_implicitfast_fluid_not_implemented(self):
@@ -229,18 +229,18 @@ class ModelIOTest(parameterized.TestCase):
<option viscosity="3.0" integrator="implicitfast"/>
<worldbody/>
</mujoco>"""),
backend_impl='jax',
impl='jax',
)
def test_wrap_inside(self):
m = test_util.load_test_file('tendon/wrap_sidesite.xml')
mx0 = mjx.put_model(m, backend_impl='jax')
mx0 = mjx.put_model(m, impl='jax')
np.testing.assert_equal(
mx0._impl.is_wrap_inside,
np.array([1, 0, 1, 0, 1, 1, 0]),
)
m.site_pos[2] = m.site_pos[1]
mx1 = mjx.put_model(m, backend_impl='jax')
mx1 = mjx.put_model(m, impl='jax')
np.testing.assert_equal(
mx1._impl.is_wrap_inside,
np.array([0, 0, 1, 0, 1, 0, 0]),
@@ -251,10 +251,10 @@ class DataIOTest(parameterized.TestCase):
"""IO tests for mjx.Data."""
@parameterized.parameters('jax', 'c')
def test_make_data(self, backend_impl: str):
def test_make_data(self, impl: str):
"""Test that make_data returns the correct shapes."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONVEX_OBJECTS)
d = mjx.make_data(m, backend_impl=backend_impl)
d = mjx.make_data(m, impl=impl)
nq = 22
nbody = 5
@@ -312,34 +312,34 @@ class DataIOTest(parameterized.TestCase):
self.assertEqual(d.qfrc_inverse.shape, (nv,))
self.assertEqual(d._impl.efc_force.shape, (nefc,))
if backend_impl == 'jax':
if impl == 'jax':
self.assertEqual(d._impl.qM.shape, (nv, nv))
self.assertEqual(d._impl.qLD.shape, (nv, nv))
self.assertEqual(d._impl.qLDiagInv.shape, (0,))
elif backend_impl == 'c':
elif impl == 'c':
self.assertEqual(d._impl.qM.shape, (nm,))
self.assertEqual(d._impl.qLD.shape, (nm,))
self.assertEqual(d._impl.qLDiagInv.shape, (nv,))
# test sparse
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE
d = mjx.make_data(m, backend_impl=backend_impl)
d = mjx.make_data(m, impl=impl)
self.assertEqual(d._impl.qM.shape, (nm,))
self.assertEqual(d._impl.qLD.shape, (nm,))
self.assertEqual(d._impl.qLDiagInv.shape, (nv,))
if backend_impl == 'c':
if impl == 'c':
# check C specific fields
self.assertEqual(d._impl.light_xpos.shape, (m.nlight, 3))
self.assertEqual(d._impl.bvh_active.shape, (m.nbvh,))
@parameterized.parameters('jax', 'c')
def test_put_data(self, backend_impl: str):
def test_put_data(self, impl: str):
"""Test that put_data puts the correct data for dense and sparse."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
# check a few fields
np.testing.assert_allclose(dx.qpos, d.qpos)
@@ -356,12 +356,12 @@ class DataIOTest(parameterized.TestCase):
)
)
if backend_impl == 'jax':
if impl == 'jax':
# check that qM is transformed properly
qm = np.zeros((m.nv, m.nv), dtype=np.float64)
mujoco.mj_fullM(m, qm, d.qM)
np.testing.assert_allclose(qm, mjx.full_m(mjx.put_model(m), dx))
elif backend_impl == 'c':
elif impl == 'c':
np.testing.assert_allclose(dx._impl.qM, d.qM)
np.testing.assert_allclose(dx._impl.qLD, d.qLD)
np.testing.assert_allclose(dx._impl.qLDiagInv, d.qLDiagInv)
@@ -417,7 +417,7 @@ class DataIOTest(parameterized.TestCase):
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx_sparse = mjx.put_data(m, d, backend_impl=backend_impl)
dx_sparse = mjx.put_data(m, d, impl=impl)
np.testing.assert_allclose(dx_sparse._impl.efc_J, dx._impl.efc_J, atol=1e-8)
# check sparse mass matrices are correct
@@ -431,25 +431,25 @@ class DataIOTest(parameterized.TestCase):
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_DENSE
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx_from_dense = mjx.put_data(m, d, backend_impl=backend_impl)
if backend_impl == 'jax':
dx_from_dense = mjx.put_data(m, d, impl=impl)
if impl == 'jax':
qm = np.zeros((m.nv, m.nv))
mujoco.mj_fullM(m, qm, d.qM)
np.testing.assert_allclose(dx_from_dense._impl.qM, qm, atol=1e-8)
elif backend_impl == 'c':
elif impl == 'c':
np.testing.assert_allclose(dx_from_dense._impl.qM, d.qM, atol=1e-8)
@parameterized.parameters(
('jax', False), ('jax', True), ('c', False), ('c', True)
)
def test_get_data(self, backend_impl: str, sparse: bool):
def test_get_data(self, impl: str, sparse: bool):
"""Test that get_data makes correct MjData."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
if sparse:
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
d_2: mujoco.MjData = mjx.get_data(m, dx)
# check a few fields
@@ -505,7 +505,7 @@ class DataIOTest(parameterized.TestCase):
np.testing.assert_allclose(d_2.efc_aref, d.efc_aref)
np.testing.assert_allclose(d_2.contact.efc_address, d.contact.efc_address)
if backend_impl == 'c':
if impl == 'c':
# check fields specific to the C implementation
np.testing.assert_allclose(d_2.bvh_active, d.bvh_active)
@@ -540,13 +540,13 @@ class DataIOTest(parameterized.TestCase):
mjx.get_data(m, dx)
@parameterized.parameters('jax', 'c')
def test_get_data_batched(self, backend_impl):
def test_get_data_batched(self, impl):
"""Test that get_data makes correct List[MjData] for batched Data."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
# second data in batch has contact dist > 0, disables contact
dx_b = jax.tree_util.tree_map(lambda x: jp.stack((x, x + 0.05)), dx)
ds = mjx.get_data(m, dx_b)
@@ -557,13 +557,13 @@ class DataIOTest(parameterized.TestCase):
self.assertEqual(ds[1].ncon, 0)
@parameterized.parameters('jax', 'c')
def test_get_data_into(self, backend_impl):
def test_get_data_into(self, impl):
"""Test that get_data_into correctly populates an MjData."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
d_2 = mujoco.MjData(m)
mjx.get_data_into(d_2, m, dx)
@@ -580,32 +580,32 @@ class DataIOTest(parameterized.TestCase):
np.testing.assert_allclose(d_2.contact.frame, d.contact.frame)
@parameterized.parameters('jax', 'c')
def test_get_data_into_wrong_shape(self, backend_impl):
def test_get_data_into_wrong_shape(self, impl):
"""Tests that get_data_into throwsif input and output shapes don't match."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
m_2 = mujoco.MjModel.from_xml_string(_MULTIPLE_CONVEX_OBJECTS)
d_2 = mujoco.MjData(m_2)
with self.assertRaisesRegex(ValueError, r'Input field.*has shape.*'):
mjx.get_data_into(d_2, m, dx)
@parameterized.parameters('jax', 'c')
def test_make_matches_put(self, backend_impl):
def test_make_matches_put(self, impl):
"""Test that make_data produces a pytree that matches put_data."""
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONSTRAINTS)
d = mujoco.MjData(m)
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d, backend_impl=backend_impl)
dx = mjx.put_data(m, d, impl=impl)
step_fn = lambda d: d.replace(time=d.time + 1)
step_fn_jit = jax.jit(step_fn).lower(dx).compile()
# placing an MjData onto device should yield the same treedef mjx.Data as
# calling make_data. they should be interchangeable for jax functions:
step_fn_jit(mjx.make_data(m, backend_impl=backend_impl))
step_fn_jit(mjx.make_data(m, impl=impl))
def test_contact_elliptic_condim1(self):
"""Test that condim=1 with ConeType.ELLIPTIC is not implemented."""
@@ -654,7 +654,7 @@ class DataIOTest(parameterized.TestCase):
</mujoco>
""")
with self.assertRaises(NotImplementedError):
mjx.put_model(m, backend_impl='jax')
mjx.put_model(m, impl='jax')
class FullCompatTest(parameterized.TestCase):
@@ -678,71 +678,71 @@ class FullCompatTest(parameterized.TestCase):
m = mujoco.MjModel.from_xml_string(xml)
with self.assertWarns(DeprecationWarning):
out = mjx_io.put_model(m, _full_compat=True)
self.assertEqual(out.backend_impl, BackendImpl.C)
self.assertEqual(out.impl, Impl.C)
with self.assertWarns(DeprecationWarning):
out = mjx_io.make_data(m, _full_compat=True)
self.assertEqual(out.backend_impl, BackendImpl.C)
self.assertEqual(out.impl, Impl.C)
# Test cases for `_resolve_backend_impl_and_device` where the device is
# Test cases for `_resolve_impl_and_device` where the device is
# specified by the user and the device is available.
_DEVICE_TEST_CASES = [
# Arguments use the following format:
# (device_type_str, backend_impl_str,
# (expected_device, expected_backend_impl)))
# (device_type_str, impl_str,
# (expected_device, expected_impl)))
# No backend specified.
('cpu', None, ('cpu', BackendImpl.C)),
('gpu-notnvidia', None, ('gpu', BackendImpl.JAX)),
('gpu-nvidia', None, ('gpu', BackendImpl.WARP)),
('tpu', None, ('tpu', BackendImpl.JAX)),
('cpu', None, ('cpu', Impl.C)),
('gpu-notnvidia', None, ('gpu', Impl.JAX)),
('gpu-nvidia', None, ('gpu', Impl.WARP)),
('tpu', None, ('tpu', Impl.JAX)),
# JAX backend specified.
('cpu', 'jax', ('cpu', BackendImpl.JAX)),
('gpu-notnvidia', 'jax', ('gpu', BackendImpl.JAX)),
('gpu-nvidia', 'jax', ('gpu', BackendImpl.JAX)),
('tpu', 'jax', ('tpu', BackendImpl.JAX)),
('cpu', 'jax', ('cpu', Impl.JAX)),
('gpu-notnvidia', 'jax', ('gpu', Impl.JAX)),
('gpu-nvidia', 'jax', ('gpu', Impl.JAX)),
('tpu', 'jax', ('tpu', Impl.JAX)),
# WARP backend specified.
('cpu', 'warp', ('cpu', 'error')),
('gpu-notnvidia', 'warp', ('cpu', 'error')),
('gpu-nvidia', 'warp', ('gpu', BackendImpl.WARP)),
('gpu-nvidia', 'warp', ('gpu', Impl.WARP)),
('tpu', 'warp', ('tpu', 'error')),
# C backend specified.
('cpu', 'c', ('cpu', BackendImpl.C)),
('cpu', 'c', ('cpu', Impl.C)),
('gpu-notnvidia', 'c', ('cpu', 'error')),
('gpu-nvidia', 'c', ('cpu', 'error')),
('tpu', 'c', ('tpu', 'error')),
]
# Test cases for `_resolve_backend_impl_and_device` where the user does NOT
# Test cases for `_resolve_impl_and_device` where the user does NOT
# specify a device. We mock the JAX default device.
_DEFAULT_DEVICE_TEST_CASES = [
# Arguments use the following format:
# (jax.default_device, backend_impl_str,
# (expected_device, expected_backend_impl))
# (jax.default_device, impl_str,
# (expected_device, expected_impl))
# No backend impl specified.
('cpu', None, ('cpu', BackendImpl.C)),
('gpu-notnvidia', None, ('gpu', BackendImpl.JAX)),
('gpu-nvidia', None, ('gpu', BackendImpl.WARP)),
('tpu', None, ('tpu', BackendImpl.JAX)),
('cpu', None, ('cpu', Impl.C)),
('gpu-notnvidia', None, ('gpu', Impl.JAX)),
('gpu-nvidia', None, ('gpu', Impl.WARP)),
('tpu', None, ('tpu', Impl.JAX)),
# JAX backend impl specified.
('cpu', 'jax', ('cpu', BackendImpl.JAX)),
('gpu-notnvidia', 'jax', ('gpu', BackendImpl.JAX)),
('gpu-nvidia', 'jax', ('gpu', BackendImpl.JAX)),
('tpu', 'jax', ('tpu', BackendImpl.JAX)),
('cpu', 'jax', ('cpu', Impl.JAX)),
('gpu-notnvidia', 'jax', ('gpu', Impl.JAX)),
('gpu-nvidia', 'jax', ('gpu', Impl.JAX)),
('tpu', 'jax', ('tpu', Impl.JAX)),
# WARP backend impl specified.
('cpu', 'warp', ('cpu', 'error')),
('gpu-notnvidia', 'warp', ('cpu', 'error')),
('gpu-nvidia', 'warp', ('gpu', BackendImpl.WARP)),
('gpu-nvidia', 'warp', ('gpu', Impl.WARP)),
('tpu', 'warp', ('tpu', 'error')),
# C backend impl specified, CPU should always be available.
('cpu', 'c', ('cpu', BackendImpl.C)),
('gpu-notnvidia', 'c', ('cpu', BackendImpl.C)),
('gpu-nvidia', 'c', ('cpu', BackendImpl.C)),
('tpu', 'c', ('cpu', BackendImpl.C)),
('cpu', 'c', ('cpu', Impl.C)),
('gpu-notnvidia', 'c', ('cpu', Impl.C)),
('gpu-nvidia', 'c', ('cpu', Impl.C)),
('tpu', 'c', ('cpu', Impl.C)),
]
class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
"""Tests for the _resolve_backend_impl_and_device function."""
class ResolveImplAndDeviceTest(parameterized.TestCase):
"""Tests for the _resolve_impl_and_device function."""
def setUp(self):
super().setUp()
@@ -783,7 +783,7 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
def test_resolve_with_device(
self,
device_type_str,
backend_impl_str,
impl_str,
expected,
):
"""Tests various combinations of device and backend impls."""
@@ -811,21 +811,21 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
self.mock_jax_devices.side_effect = devices_side_effect
expected_device, expected_backend_impl = expected
if expected_backend_impl == 'error':
expected_device, expected_impl = expected
if expected_impl == 'error':
with self.assertRaises(AssertionError):
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=input_device
mjx_io._resolve_impl_and_device(
impl=impl_str, device=input_device
)
return
actual_backend_impl, actual_device = (
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=input_device
actual_impl, actual_device = (
mjx_io._resolve_impl_and_device(
impl=impl_str, device=input_device
)
)
self.assertEqual(actual_backend_impl, expected_backend_impl)
self.assertEqual(actual_impl, expected_impl)
self.assertIsNotNone(actual_device)
self.assertEqual(actual_device.platform, expected_device)
@@ -839,7 +839,7 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
def test_resolve_without_device(
self,
default_device_str,
backend_impl_str,
impl_str,
expected,
):
"""Tests various combinations of jax.default_device and backend impls."""
@@ -878,32 +878,32 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
lambda: default_device_side_effect_str
)
expected_device, expected_backend_impl = expected
expected_device, expected_impl = expected
if (
expected_backend_impl == 'error'
expected_impl == 'error'
and default_device_str != 'gpu-nvidia'
and backend_impl_str == 'warp'
and impl_str == 'warp'
):
with self.assertRaisesRegex(RuntimeError, 'cuda backend not supported'):
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=None
mjx_io._resolve_impl_and_device(
impl=impl_str, device=None
)
return
if expected_backend_impl == 'error':
if expected_impl == 'error':
with self.assertRaises(AssertionError):
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=None
mjx_io._resolve_impl_and_device(
impl=impl_str, device=None
)
return
actual_backend_impl, actual_device = (
mjx_io._resolve_backend_impl_and_device(
backend_impl=backend_impl_str, device=None
actual_impl, actual_device = (
mjx_io._resolve_impl_and_device(
impl=impl_str, device=None
)
)
self.assertEqual(actual_backend_impl, expected_backend_impl)
self.assertEqual(actual_impl, expected_impl)
self.assertIsNotNone(actual_device)
self.assertEqual(actual_device.platform, expected_device)
@@ -918,26 +918,26 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
self.mock_default_backend.side_effect = lambda: 'gpu'
# Default to JAX instead of WARP on NVIDIA GPU.
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl=None, device=None
impl, device = mjx_io._resolve_impl_and_device(
impl=None, device=None
)
self.assertEqual(backend_impl, BackendImpl.JAX)
self.assertEqual(impl, Impl.JAX)
self.assertEqual(device.platform, 'gpu')
# Specifying an NVIDIA GPU should still choose JAX.
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl=None, device=self.mock_nvidia_gpu
impl, device = mjx_io._resolve_impl_and_device(
impl=None, device=self.mock_nvidia_gpu
)
self.assertEqual(backend_impl, BackendImpl.JAX)
self.assertEqual(impl, Impl.JAX)
self.assertEqual(device.platform, 'gpu')
# Requesting warp explicitly should fail since it is disabled.
with self.assertRaises(AssertionError):
mjx_io._resolve_backend_impl_and_device(
backend_impl='warp', device=self.mock_nvidia_gpu
mjx_io._resolve_impl_and_device(
impl='warp', device=self.mock_nvidia_gpu
)
with self.assertRaises(AssertionError):
mjx_io._resolve_backend_impl_and_device(backend_impl='warp', device=None)
mjx_io._resolve_impl_and_device(impl='warp', device=None)
@mock.patch.dict(os.environ, {'MJX_C_DEFAULT_ENABLED': 'false'})
def test_resolve_c_disabled(self):
@@ -950,30 +950,30 @@ class ResolveBackendImplAndDeviceTest(parameterized.TestCase):
self.mock_default_backend.side_effect = lambda: 'cpu'
# Default to JAX instead of C on CPU.
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl=None, device=None
impl, device = mjx_io._resolve_impl_and_device(
impl=None, device=None
)
self.assertEqual(backend_impl, BackendImpl.JAX)
self.assertEqual(impl, Impl.JAX)
self.assertEqual(device.platform, 'cpu')
# Specifing CPU should still choose JAX.
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl=None, device=self.mock_cpu
impl, device = mjx_io._resolve_impl_and_device(
impl=None, device=self.mock_cpu
)
self.assertEqual(backend_impl, BackendImpl.JAX)
self.assertEqual(impl, Impl.JAX)
self.assertEqual(device.platform, 'cpu')
# Specifying C should choose C!
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl='c', device=None
impl, device = mjx_io._resolve_impl_and_device(
impl='c', device=None
)
self.assertEqual(backend_impl, BackendImpl.C)
self.assertEqual(impl, Impl.C)
self.assertEqual(device.platform, 'cpu')
backend_impl, device = mjx_io._resolve_backend_impl_and_device(
backend_impl='c', device=self.mock_cpu
impl, device = mjx_io._resolve_impl_and_device(
impl='c', device=self.mock_cpu
)
self.assertEqual(backend_impl, BackendImpl.C)
self.assertEqual(impl, Impl.C)
self.assertEqual(device.platform, 'cpu')
+1 -1
View File
@@ -22,7 +22,7 @@ from mujoco.mjx._src import math
from mujoco.mjx._src import ray
from mujoco.mjx._src import smooth
from mujoco.mjx._src import support
from mujoco.mjx._src.types import BackendImpl
from mujoco.mjx._src.types import Impl
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import DataJAX
from mujoco.mjx._src.types import DisableBit
+9 -9
View File
@@ -24,8 +24,8 @@ from mujoco.mjx._src.dataclasses import PyTreeNode # pylint: disable=g-importin
import numpy as np
class BackendImpl(enum.Enum):
"""Backend implementation to use."""
class Impl(enum.Enum):
"""Implementation to use."""
C = 'c'
JAX = 'jax'
@@ -34,7 +34,7 @@ class BackendImpl(enum.Enum):
@classmethod
def _missing_(cls, value):
# This method is called only when lookup by value fails
# (e.g., BackendImpl('JAX') fails initially because 'JAX' != 'jax')
# (e.g., Impl('JAX') fails initially because 'JAX' != 'jax')
if not isinstance(value, str):
return None
for member in cls:
@@ -871,10 +871,10 @@ class Model(PyTreeNode):
_impl: Union[ModelC, ModelJAX]
@property
def backend_impl(self) -> BackendImpl:
def impl(self) -> Impl:
return {
ModelC: BackendImpl.C,
ModelJAX: BackendImpl.JAX,
ModelC: Impl.C,
ModelJAX: Impl.JAX,
}[type(self._impl)]
def __getattr__(self, name: str):
@@ -1119,10 +1119,10 @@ class Data(PyTreeNode):
_impl: Union[DataC, DataJAX]
@property
def backend_impl(self) -> BackendImpl:
def impl(self) -> Impl:
return {
DataC: BackendImpl.C,
DataJAX: BackendImpl.JAX,
DataC: Impl.C,
DataJAX: Impl.JAX,
}[type(self._impl)]
def __getattr__(self, name: str):