Download code/snow_rl/sinkage.py from iteratehack/snow-rl-baseline: direct link, hf CLI and curl.
- Browser
- Download file 6.27 kB
-
https://huggingface.co/iteratehack/snow-rl-baseline/resolve/main/code/snow_rl/sinkage.py
- Command line
-
hf download hf://iteratehack/snow-rl-baseline/code/snow_rl/sinkage.py
-
curl -L -o sinkage.py https://huggingface.co/iteratehack/snow-rl-baseline/resolve/main/code/snow_rl/sinkage.py
6.27 kB
| """Snow sinkage force (Step 2, part 2). | |
| Follows the resistance law in hackathon-everest's `physics.py::_resistance_n`: | |
| F = k_eff * d^1.12 + c * max(v_down, 0) + 3 * k_eff * max(d - d_support, 0) | |
| with a void band that collapses resistance to 8%. Term by term: | |
| * `k_eff * d^1.12` progressive compaction -- snow stiffens as it packs. | |
| * `c * v_down` rate dependence -- snow resists a fast foot harder than a slow one. | |
| Only the downward half; snow does not pull a lifting foot back. | |
| * `3 * k_eff * (...)` bottoming out on the hard substrate once the foot passes through | |
| the snow layer. The factor 3 makes that transition sharp. | |
| * void a hollow under a crust: the foot meets almost nothing, then the | |
| substrate term catches it. This is the breakthrough the policy must survive. | |
| Applied to the feet as `xfrc_applied`, not as contact geometry -- see terrain.py for why. | |
| The brief calls for depth-and-velocity dependence and explicitly not granular physics. | |
| """ | |
| from __future__ import annotations | |
| import jax | |
| import jax.numpy as jnp | |
| COMPACTION_EXPONENT = 1.12 | |
| SUBSTRATE_STIFFNESS_GAIN = 3.0 | |
| VOID_RESISTANCE_FACTOR = 0.08 | |
| MAX_FORCE_N = 2_000.0 # numerical guard; ~6x G1 body weight | |
| # Ploughing. Vertical support alone is NOT enough to make deep snow hard: under uniform | |
| # stiffness a foot reaches the same equilibrium sinkage in 0.07 m and 0.19 m of snow, so | |
| # the two regimes would feel identical and the policy would have nothing to learn. | |
| # What actually punishes deep snow is dragging the swing foot through it. Resistance scales | |
| # with submerged frontal area (~ depth x foot width) and snow strength, saturating in speed. | |
| # Sized so a foot swung through deep snow at walking speed meets ~30 N -- roughly 9% of body | |
| # weight, enough to alter gait without immobilising the robot. The learning signal comes from | |
| # clearance: a foot lifted clear of the surface ploughs nothing, so deeper snow demands a | |
| # higher swing to escape the same drag. | |
| PLOUGH_COEFF = 0.25 | |
| FOOT_WIDTH_M = 0.09 # G1 foot, approximate | |
| PLOUGH_REFERENCE_SPEED_MPS = 0.4 | |
| def resistance_n( | |
| penetration_m: jax.Array, | |
| vertical_velocity_mps: jax.Array, | |
| snow_depth_m: jax.Array, | |
| stiffness_n_per_m: jax.Array, | |
| damping_ns_per_m: jax.Array, | |
| void: jax.Array, | |
| include_substrate: bool = True, | |
| ) -> jax.Array: | |
| """Upward resisting force on a foot. Zero when the foot is above the snow surface. | |
| penetration_m: depth below the snow surface, negative when clear of it. | |
| vertical_velocity_mps: foot velocity, negative downward. | |
| include_substrate: whether to add the bottoming-out term. TRUE when deriving bearing | |
| capacity as a label (everest has no geometry, so the term is the only hard ground there). | |
| FALSE when computing a force to APPLY in MuJoCo, where the substrate is a real plane with | |
| a real contact solver -- adding it there double-counts the floor and fights the solver, | |
| which produced spikes to 1530 N (~4.5x body weight) against the 2000 N clamp. | |
| """ | |
| d = jnp.maximum(penetration_m, 0.0) | |
| v_down = jnp.maximum(-vertical_velocity_mps, 0.0) | |
| force = stiffness_n_per_m * d**COMPACTION_EXPONENT + damping_ns_per_m * v_down | |
| # Void collapses the snow-column term only; the substrate below still stops the foot. | |
| force = jnp.where(void, force * VOID_RESISTANCE_FACTOR, force) | |
| if include_substrate: | |
| force = force + SUBSTRATE_STIFFNESS_GAIN * stiffness_n_per_m * jnp.maximum( | |
| d - snow_depth_m, 0.0 | |
| ) | |
| return jnp.clip(jnp.where(d > 0.0, force, 0.0), 0.0, MAX_FORCE_N) | |
| def plough_force_n( | |
| penetration_m: jax.Array, | |
| horizontal_speed_mps: jax.Array, | |
| stiffness_n_per_m: jax.Array, | |
| void: jax.Array, | |
| ) -> jax.Array: | |
| """Magnitude of the horizontal drag on a foot moving through snow. | |
| Opposes horizontal motion. Zero above the surface; a void offers almost nothing to plough. | |
| """ | |
| submerged = jnp.maximum(penetration_m, 0.0) | |
| strength = jnp.where(void, stiffness_n_per_m * VOID_RESISTANCE_FACTOR, stiffness_n_per_m) | |
| force = ( | |
| PLOUGH_COEFF * strength * submerged * FOOT_WIDTH_M | |
| * jnp.tanh(horizontal_speed_mps / PLOUGH_REFERENCE_SPEED_MPS) | |
| ) | |
| return jnp.clip(force, 0.0, MAX_FORCE_N) | |
| def foot_force_world_n( | |
| foot_pos_z_m: jax.Array, | |
| foot_vel_xyz_mps: jax.Array, | |
| ground_z_m: jax.Array, | |
| snow_depth_m: jax.Array, | |
| stiffness_n_per_m: jax.Array, | |
| damping_ns_per_m: jax.Array, | |
| void: jax.Array, | |
| ) -> jax.Array: | |
| """World-frame (fx, fy, fz) per foot, ready for `data.xfrc_applied[body, :3]`. | |
| The snow surface sits at `ground_z_m + snow_depth_m`; the hard substrate at `ground_z_m`. | |
| Vertical term supports the foot; horizontal term ploughs against its motion. | |
| """ | |
| penetration = ground_z_m + snow_depth_m - foot_pos_z_m | |
| # include_substrate=False: MuJoCo's floor plane already stops the foot. | |
| fz = resistance_n(penetration, foot_vel_xyz_mps[..., 2], snow_depth_m, | |
| stiffness_n_per_m, damping_ns_per_m, void, include_substrate=False) | |
| v_h = foot_vel_xyz_mps[..., :2] | |
| speed = jnp.linalg.norm(v_h, axis=-1) | |
| drag = plough_force_n(penetration, speed, stiffness_n_per_m, void) | |
| direction = v_h / jnp.maximum(speed, 1e-6)[..., None] | |
| return jnp.concatenate([-drag[..., None] * direction, fz[..., None]], axis=-1) | |
| def equilibrium_sinkage_m( | |
| load_n: jax.Array, | |
| snow_depth_m: jax.Array, | |
| stiffness_n_per_m: jax.Array, | |
| void: jax.Array, | |
| *, | |
| iterations: int = 40, | |
| ) -> jax.Array: | |
| """Static sinkage where resistance balances `load_n`, by bisection. | |
| Diagnostic and test helper -- the training loop integrates the force instead. | |
| """ | |
| def body(bounds, _): | |
| lo, hi = bounds | |
| mid = 0.5 * (lo + hi) | |
| f = resistance_n(mid, jnp.zeros_like(mid), snow_depth_m, stiffness_n_per_m, | |
| jnp.zeros_like(mid), void) | |
| too_soft = f < load_n | |
| return (jnp.where(too_soft, mid, lo), jnp.where(too_soft, hi, mid)), None | |
| lo = jnp.zeros_like(snow_depth_m) | |
| hi = jnp.full_like(snow_depth_m, 1.0) | |
| (lo, hi), _ = jax.lax.scan(body, (lo, hi), None, length=iterations) | |
| return 0.5 * (lo + hi) | |