"""Tests for go1_constants.py.""" import re import mujoco import numpy as np import pytest from mjlab.asset_zoo.robots.unitree_go1 import go1_constants from mjlab.entity import Entity from mjlab.utils.string import resolve_expr @pytest.fixture(scope="module") def go1_entity() -> Entity: return Entity(go1_constants.get_go1_robot_cfg()) @pytest.fixture(scope="module") def go1_model(go1_entity: Entity) -> mujoco.MjModel: return go1_entity.spec.compile() # fmt: off @pytest.mark.parametrize( "actuator_config,stiffness,damping", [ (go1_constants.GO1_HIP_ACTUATOR_CFG, go1_constants.STIFFNESS_HIP, go1_constants.DAMPING_HIP), (go1_constants.GO1_KNEE_ACTUATOR_CFG, go1_constants.STIFFNESS_KNEE, go1_constants.DAMPING_KNEE), ], ) # fmt: on def test_actuator_parameters(go1_model, actuator_config, stiffness, damping): for i in range(go1_model.nu): actuator = go1_model.actuator(i) actuator_name = actuator.name matches = any( re.match(pattern, actuator_name) for pattern in actuator_config.target_names_expr ) if matches: assert actuator.gainprm[0] == stiffness assert actuator.biasprm[1] == -stiffness assert actuator.biasprm[2] == -damping assert actuator.forcerange[0] == -actuator_config.effort_limit assert actuator.forcerange[1] == actuator_config.effort_limit def test_keyframe_joint_positions(go1_entity, go1_model) -> None: """Test that keyframe joint positions match the configuration.""" key = go1_model.key("init_state") expected_joint_pos = go1_constants.INIT_STATE.joint_pos assert expected_joint_pos is not None expected_values = resolve_expr(expected_joint_pos, go1_entity.joint_names, 0.0) for joint_name, expected_value in zip( go1_entity.joint_names, expected_values, strict=True ): joint = go1_model.joint(joint_name) qpos_idx = joint.qposadr[0] actual_value = key.qpos[qpos_idx] np.testing.assert_allclose( actual_value, expected_value, rtol=1e-5, err_msg=f"Joint {joint_name} position mismatch: " f"expected {expected_value}, got {actual_value}", ) def test_foot_collision_geoms(go1_model) -> None: """Foot collision geoms should have specific properties.""" foot_pattern = r"^[FR][LR]_foot_collision$" for i in range(go1_model.ngeom): geom = go1_model.geom(i) if re.match(foot_pattern, geom.name): assert geom.condim == 6 assert geom.priority == 1 assert geom.friction[0] == 1.0 def test_all_collision_geoms_enabled(go1_model) -> None: """Every *_collision geom, including numbered ones, must stay collidable. The go1 XML numbers its secondary collision capsules (FR_thigh_collision1, ...), which a pattern anchored at "_collision" would silently drop, letting disable_other_geoms zero their contype. Guard against that regression with an oracle independent of the config's own regex resolution. """ collision_geoms = [ go1_model.geom(i) for i in range(go1_model.ngeom) if "_collision" in go1_model.geom(i).name ] assert len(collision_geoms) == 30 for geom in collision_geoms: assert geom.contype == 1, f"{geom.name} lost its contype" assert geom.conaffinity == 1, f"{geom.name} lost its conaffinity" def test_collision_geom_count(go1_model) -> None: """Go1 should have 4 foot collision geoms.""" foot_pattern = r"^[FR][LR]_foot_collision$" foot_geoms = [ g.name for g in [go1_model.geom(i) for i in range(go1_model.ngeom)] if re.match(foot_pattern, g.name) ] assert len(foot_geoms) == 4 def test_go1_entity_creation(go1_entity) -> None: """Test basic Go1 entity properties.""" assert go1_entity.num_actuators == 12 assert go1_entity.num_joints == 12 assert go1_entity.is_actuated assert not go1_entity.is_fixed_base