Instructions to use Histochemichael/microduck-electric-slide-policy with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Microduck
How to use Histochemichael/microduck-electric-slide-policy with Microduck:
# Replace SLOT with the slot specified in the model card (walk, stand, sitstand, ground_pick, kick_left, kick_right, roulade). sudo robotctl policy load SLOT Histochemichael/microduck-electric-slide-policy
- Notebooks
- Google Colab
- Kaggle
Download code/physics_choreography.py from Histochemichael/microduck-electric-slide-policy: direct link, hf CLI and curl.
- Browser
- Download file 6.05 kB
-
https://huggingface.co/Histochemichael/microduck-electric-slide-policy/resolve/main/code/physics_choreography.py
- Command line
-
hf download hf://Histochemichael/microduck-electric-slide-policy/code/physics_choreography.py
-
curl -L -o physics_choreography.py https://huggingface.co/Histochemichael/microduck-electric-slide-policy/resolve/main/code/physics_choreography.py
6.05 kB
| """Electric Slide commands for the official physics-based walking policy.""" | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import math | |
| from pathlib import Path | |
| import numpy as np | |
| from choreography import BEAT_SECONDS, COUNTS_PER_WALL, SHOW_BEATS, SHOW_SECONDS | |
| class PolicyCommand: | |
| twist: tuple[float, float, float] | |
| head_pose: tuple[float, float, float, float] | |
| body_pose: tuple[float, float, float, float, float, float] | |
| # Body-frame velocity commands for each count: forward, lateral, yaw rate. | |
| # Pollen's current simulator deliberately leaves the lateral slot at zero, | |
| # and this published policy does not produce a backward gait. The dance | |
| # therefore uses supported forward footfalls plus mirrored arcs for its | |
| # right/left phrases. The second half bends through one physical wall turn. | |
| COUNT_TWISTS = ( | |
| (0.22, 0.00, 0.65), | |
| (0.22, 0.00, 0.65), | |
| (0.22, 0.00, 0.65), | |
| (0.00, 0.00, 0.00), | |
| (0.22, 0.00, -0.65), | |
| (0.22, 0.00, -0.65), | |
| (0.22, 0.00, -0.65), | |
| (0.00, 0.00, 0.00), | |
| (0.18, 0.00, 1.00), | |
| (0.18, 0.00, 1.00), | |
| (0.18, 0.00, 1.00), | |
| (0.00, 0.00, 1.00), | |
| (0.00, 0.00, 0.00), | |
| (0.14, 0.00, 1.00), | |
| (0.00, 0.00, 0.00), | |
| (0.10, 0.00, 1.00), | |
| (0.10, 0.00, 1.00), | |
| (0.10, 0.00, 1.00), | |
| ) | |
| MOTION_TIMELINE = ( | |
| Path(__file__).resolve().parent | |
| / "generated/learned_motion_retarget_50hz.npz" | |
| ) | |
| _TIMELINE_CACHE: dict[str, np.ndarray] | None = None | |
| def _timeline() -> dict[str, np.ndarray] | None: | |
| global _TIMELINE_CACHE | |
| if _TIMELINE_CACHE is None and MOTION_TIMELINE.exists(): | |
| data = np.load(MOTION_TIMELINE, allow_pickle=True) | |
| _TIMELINE_CACHE = {name: data[name] for name in data.files} | |
| return _TIMELINE_CACHE | |
| def _motion_sample(time_seconds: float) -> tuple[np.ndarray, int] | None: | |
| data = _timeline() | |
| if data is None: | |
| return None | |
| commands = data["command_13d"] | |
| control_hz = float(data["control_hz"]) | |
| phase = (time_seconds % SHOW_SECONDS) * control_hz | |
| lower = int(math.floor(phase)) % len(commands) | |
| upper = (lower + 1) % len(commands) | |
| blend = phase - math.floor(phase) | |
| command = commands[lower] * (1.0 - blend) + commands[upper] * blend | |
| policy_code = int(data["policy_code"][lower]) | |
| return command, policy_code | |
| def _smoothstep(value: float) -> float: | |
| value = max(0.0, min(1.0, value)) | |
| return value * value * (3.0 - 2.0 * value) | |
| def _blended_twist(beat: float) -> tuple[float, float, float]: | |
| count = int(math.floor(beat)) % COUNTS_PER_WALL | |
| phase = beat - math.floor(beat) | |
| current = COUNT_TWISTS[count] | |
| previous = COUNT_TWISTS[(count - 1) % COUNTS_PER_WALL] | |
| # Do not carry the prior wall's turn into the next grapevine. | |
| if count == 0: | |
| previous = current | |
| blend = _smoothstep(phase / 0.10) | |
| return tuple(previous[i] + (current[i] - previous[i]) * blend for i in range(3)) | |
| def command_for_time(time_seconds: float, duck_index: int = 0) -> PolicyCommand: | |
| """Return the 61D-policy command blocks for one show instant.""" | |
| motion = _motion_sample(time_seconds) | |
| if motion is not None: | |
| command, _ = motion | |
| return PolicyCommand( | |
| twist=tuple(float(value) for value in command[0:3]), | |
| head_pose=tuple(float(value) for value in command[3:7]), | |
| body_pose=tuple(float(value) for value in command[7:13]), | |
| ) | |
| beat = (time_seconds / BEAT_SECONDS) % SHOW_BEATS | |
| count = int(math.floor(beat)) % COUNTS_PER_WALL | |
| phase = beat - math.floor(beat) | |
| pulse = math.sin(math.pi * phase) | |
| # Keep the physics commands exactly synchronized. Even small pose-command | |
| # phase offsets can amplify into formation drift through the learned gait. | |
| flock_phase = 0.0 | |
| groove = math.sin(2.0 * math.pi * (beat / 2.0 + flock_phase)) | |
| twist = _blended_twist(beat) | |
| # Head attitude stays within the command ranges used in training. | |
| neck_pitch = 0.05 * groove | |
| head_pitch = -0.05 * groove | |
| head_yaw = 0.10 * math.sin(2.0 * math.pi * (beat / 4.0 + flock_phase)) | |
| head_roll = 0.09 * groove | |
| body_z = -0.004 * abs(groove) | |
| body_roll = 0.035 * groove | |
| body_pitch = 0.018 * math.sin(2.0 * math.pi * beat / 4.0) | |
| # Character section: grounded scoop, relaxed lean, then head leads turn. | |
| if count in (12, 13): | |
| amount = _smoothstep(phase) if count == 12 else pulse | |
| body_z -= 0.010 * amount | |
| body_pitch += 0.035 * amount | |
| neck_pitch += 0.08 * amount | |
| head_pitch -= 0.06 * amount | |
| head_yaw += 0.08 * amount | |
| elif count in (14, 15): | |
| amount = _smoothstep(phase) if count == 14 else pulse | |
| body_pitch -= 0.035 * amount | |
| neck_pitch -= 0.06 * amount | |
| head_pitch += 0.05 * amount | |
| elif count in (16, 17): | |
| head_yaw += 0.20 * pulse | |
| head_roll += 0.04 * groove | |
| return PolicyCommand( | |
| twist=twist, | |
| head_pose=(neck_pitch, head_pitch, head_yaw, head_roll), | |
| body_pose=(0.0, 0.0, body_z, body_roll, body_pitch, 0.0), | |
| ) | |
| def policy_for_time(time_seconds: float) -> str: | |
| """Choose official one-shot kick policies for the two accent counts.""" | |
| motion = _motion_sample(time_seconds) | |
| if motion is not None: | |
| _, policy_code = motion | |
| return ("walk", "kick_left", "kick_right")[policy_code] | |
| beat = (time_seconds / BEAT_SECONDS) % SHOW_BEATS | |
| count = int(math.floor(beat)) % COUNTS_PER_WALL | |
| if count == 12: | |
| return "kick_left" | |
| if count == 14: | |
| return "kick_right" | |
| return "walk" | |
| def integrated_turn_radians(samples: int = 10000) -> float: | |
| """Numerically integrate one phrase's yaw command for a regression test.""" | |
| duration = COUNTS_PER_WALL * BEAT_SECONDS | |
| dt = duration / samples | |
| return sum(command_for_time((index + 0.5) * dt).twist[2] * dt for index in range(samples)) | |
| __all__ = [ | |
| "COUNT_TWISTS", | |
| "PolicyCommand", | |
| "SHOW_SECONDS", | |
| "command_for_time", | |
| "integrated_turn_radians", | |
| "policy_for_time", | |
| ] | |