Upstream: https://github.com/pollen-robotics/microduck_rl Upstream-Commit: d424a0c899f6b33cbd3daeb279913134349c0b63 Upstream-Branch: develop
111 lines
4.4 KiB
Python
111 lines
4.4 KiB
Python
from mjlab_microduck.tasks import mdp as microduck_mdp
|
|
from mjlab_microduck.tasks.microduck_spin_env_cfg import (
|
|
make_microduck_spin_env_cfg,
|
|
MicroduckSpinRlCfg,
|
|
)
|
|
|
|
|
|
def test_cfg_uses_phase_command_with_runtime_default_period():
|
|
cfg = make_microduck_spin_env_cfg()
|
|
cmd = cfg.commands["twist"]
|
|
assert isinstance(cmd, microduck_mdp.GroundPickPhaseCommandCfg)
|
|
# 4.0 s = le défaut de --ground-pick-period : rien à passer au runtime
|
|
assert cmd.period == 4.0
|
|
# chaque épisode démarre à phase 0 (debout), comme le bouton au déploiement
|
|
assert cmd.randomize_phase is False
|
|
|
|
|
|
def test_cfg_has_the_spin_rewards():
|
|
cfg = make_microduck_spin_env_cfg()
|
|
for name in (
|
|
"spin_rate_track",
|
|
"spin_rate_l1",
|
|
"spin_stay_in_place",
|
|
"spin_wheel_differential",
|
|
"spin_grounded",
|
|
"leg_antisymmetry",
|
|
):
|
|
assert name in cfg.rewards, name
|
|
# objectif principal avec un poids dominant
|
|
assert cfg.rewards["spin_rate_track"].weight == 6.0
|
|
# sur-place est un COÛT
|
|
assert cfg.rewards["spin_stay_in_place"].weight < 0.0
|
|
|
|
|
|
def test_stay_in_place_is_attenuated_during_the_launch_ramp():
|
|
# Renforcé à -3.0, ce terme s'opposerait à l'injection de moment angulaire s'il
|
|
# était plein tarif pendant la rampe de lancement : il doit y être atténué.
|
|
cfg = make_microduck_spin_env_cfg()
|
|
params = cfg.rewards["spin_stay_in_place"].params
|
|
assert 0.0 < params["launch_scale"] < 1.0
|
|
assert params["accel_end"] == microduck_mdp.SPIN_ACCEL_END
|
|
# cible positive = anti-horaire (le sens est porté par l'enveloppe)
|
|
assert microduck_mdp.SPIN_RATE_MAX > 0.0
|
|
|
|
|
|
def test_angular_momentum_reward_is_removed():
|
|
# Régression : angular_momentum_penalty pénalise la NORME 3D du moment
|
|
# angulaire, elle combattrait directement le spin. Elle doit être absente.
|
|
cfg = make_microduck_spin_env_cfg()
|
|
assert "angular_momentum" not in cfg.rewards
|
|
# body_ang_vel ne pénalise que x/y -> elle reste, elle mate le ballant
|
|
assert "body_ang_vel" in cfg.rewards
|
|
|
|
|
|
def test_head_yaw_is_free_to_act_as_a_flywheel():
|
|
cfg = make_microduck_spin_env_cfg()
|
|
pattern = cfg.rewards["neck_joint_pos_l2"].params["pattern"]
|
|
assert "head_yaw" not in pattern
|
|
|
|
|
|
def test_entry_velocity_allows_standstill_and_slow_roll():
|
|
cfg = make_microduck_spin_env_cfg()
|
|
# jamais via un push en mode reset (régression NaN du crouch)
|
|
assert "entry_velocity" not in cfg.events
|
|
lo, hi = cfg.events["reset_base"].params["velocity_range"]["x"]
|
|
assert lo == 0.0 and hi > 0.0
|
|
|
|
|
|
def test_symmetry_augmentation_is_disabled():
|
|
# la symétrie G/D transformerait un spin à gauche en spin à droite
|
|
assert MicroduckSpinRlCfg.algorithm.symmetry_cfg is None
|
|
|
|
|
|
def test_leg_antisymmetry_shaping_decays():
|
|
cfg = make_microduck_spin_env_cfg()
|
|
stages = cfg.curriculum["leg_antisym_weight"].params["weight_stages"]
|
|
weights = [s["weight"] for s in stages]
|
|
assert weights[0] == cfg.rewards["leg_antisymmetry"].weight
|
|
assert weights == sorted(weights, reverse=True)
|
|
assert weights[-1] < weights[0]
|
|
|
|
|
|
def test_actor_observation_keeps_the_61d_slot_layout():
|
|
# condition pour que l'ONNX charge dans le slot du runtime. L'égalité exacte
|
|
# des dimensions avec le crouch est vérifiée par test_obs_parity_with_roller_crouch
|
|
# ci-dessous ; ici on vérifie la structure.
|
|
cfg = make_microduck_spin_env_cfg()
|
|
terms = cfg.observations["actor"].terms
|
|
assert "base_lin_vel" not in terms
|
|
assert "height_scan" not in terms
|
|
for padded in ("head_command", "body_command"):
|
|
assert padded in terms
|
|
assert terms["head_command"].params["dim"] == 4
|
|
assert terms["body_command"].params["dim"] == 6
|
|
|
|
|
|
def test_obs_parity_with_roller_crouch():
|
|
# Parité de layout obligatoire : sinon l'ONNX exporté ne charge pas dans le
|
|
# slot du runtime. Contrairement au test de structure ci-dessus, celui-ci
|
|
# compare l'ordre EXACT des termes, groupe par groupe.
|
|
from mjlab_microduck.tasks.microduck_roller_crouch_env_cfg import (
|
|
make_microduck_roller_crouch_env_cfg,
|
|
)
|
|
|
|
spin = make_microduck_spin_env_cfg()
|
|
crouch = make_microduck_roller_crouch_env_cfg()
|
|
for grp in ("actor", "critic"):
|
|
assert list(spin.observations[grp].terms.keys()) == list(
|
|
crouch.observations[grp].terms.keys()
|
|
), f"layout d'observation divergent sur le groupe {grp}"
|