Upload folder using huggingface_hub
Browse files- code/snow_rl/snow_env.py +12 -15
- code/snow_rl/terrain.py +14 -4
code/snow_rl/snow_env.py
CHANGED
|
@@ -76,22 +76,15 @@ def default_config() -> config_dict.ConfigDict:
|
|
| 76 |
# its gait for snow. Scaled up ~3.5x to reach roughly a quarter of total reward: enough
|
| 77 |
# to steer, short of drowning velocity tracking (whose failure mode is a policy that
|
| 78 |
# stands still or creeps).
|
| 79 |
-
#
|
| 80 |
-
# 0.4 -> 1.5 -> 3.0
|
| 81 |
-
#
|
| 82 |
-
#
|
| 83 |
-
#
|
| 84 |
-
|
| 85 |
-
# v4 1% swing 0.082 survived 491 <- standing still
|
| 86 |
-
#
|
| 87 |
-
# Survival time is a trap as a headline metric: not moving maximises it. Distance and
|
| 88 |
-
# velocity tracking are the honest measures, and by those v1 was the best policy. Back
|
| 89 |
-
# toward v1's balance, keeping the two changes that were real improvements (a reachable
|
| 90 |
-
# stumble term, and terrain that couples depth to stiffness).
|
| 91 |
-
cfg.reward_config.scales.snow_clearance = 1.5
|
| 92 |
cfg.reward_config.scales.snow_stumble = -1.0
|
| 93 |
-
cfg.reward_config.scales.snow_flat_efficiency = -0.
|
| 94 |
-
cfg.reward_config.scales.snow_overload = -0.
|
| 95 |
cfg.snow = config_dict.create(
|
| 96 |
enable_forces=True,
|
| 97 |
# Zeroing this blanks the estimator block in the observation while leaving snow
|
|
@@ -105,6 +98,9 @@ def default_config() -> config_dict.ConfigDict:
|
|
| 105 |
false_safe_rate=-1.0, # <0 means "use the calibrated point-estimate rate"
|
| 106 |
terrain_bank_size=TERRAIN_BANK_SIZE,
|
| 107 |
terrain_seed=0,
|
|
|
|
|
|
|
|
|
|
| 108 |
)
|
| 109 |
return cfg
|
| 110 |
|
|
@@ -139,6 +135,7 @@ class SnowJoystick(joystick.Joystick):
|
|
| 139 |
jax.random.split(jax.random.key(self._config.snow.terrain_seed),
|
| 140 |
self._config.snow.terrain_bank_size),
|
| 141 |
self._terrain_cfg,
|
|
|
|
| 142 |
)
|
| 143 |
|
| 144 |
# ---------------- terrain ----------------
|
|
|
|
| 76 |
# its gait for snow. Scaled up ~3.5x to reach roughly a quarter of total reward: enough
|
| 77 |
# to steer, short of drowning velocity tracking (whose failure mode is a policy that
|
| 78 |
# stands still or creeps).
|
| 79 |
+
# Back to the brief's original weights. Five tuning cycles (0.8 -> 3 -> 5 on clearance,
|
| 80 |
+
# 0.4 -> 1.5 -> 3.0 on overload) never beat them: measured velocity tracking went
|
| 81 |
+
# 18% -> -5% -> -8% while survival rose, because not walking maximises survival. The
|
| 82 |
+
# bottleneck was never the reward weights -- it is that a flat-ground policy is thrown
|
| 83 |
+
# into 200 mm snow from step 0, which the curriculum now addresses instead.
|
| 84 |
+
cfg.reward_config.scales.snow_clearance = 0.8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
cfg.reward_config.scales.snow_stumble = -1.0
|
| 86 |
+
cfg.reward_config.scales.snow_flat_efficiency = -0.5
|
| 87 |
+
cfg.reward_config.scales.snow_overload = -0.4
|
| 88 |
cfg.snow = config_dict.create(
|
| 89 |
enable_forces=True,
|
| 90 |
# Zeroing this blanks the estimator block in the observation while leaving snow
|
|
|
|
| 98 |
false_safe_rate=-1.0, # <0 means "use the calibrated point-estimate rate"
|
| 99 |
terrain_bank_size=TERRAIN_BANK_SIZE,
|
| 100 |
terrain_seed=0,
|
| 101 |
+
# Curriculum: 1.0 is full difficulty (0-25 cm). Lower values shrink every regime's
|
| 102 |
+
# depth range so an early stage trains on snow the policy can actually cope with.
|
| 103 |
+
depth_scale=1.0,
|
| 104 |
)
|
| 105 |
return cfg
|
| 106 |
|
|
|
|
| 135 |
jax.random.split(jax.random.key(self._config.snow.terrain_seed),
|
| 136 |
self._config.snow.terrain_bank_size),
|
| 137 |
self._terrain_cfg,
|
| 138 |
+
float(self._config.snow.depth_scale),
|
| 139 |
)
|
| 140 |
|
| 141 |
# ---------------- terrain ----------------
|
code/snow_rl/terrain.py
CHANGED
|
@@ -112,15 +112,25 @@ def _correlated_unit_field(key: jax.Array, cfg: TerrainConfig) -> jax.Array:
|
|
| 112 |
return jnp.clip((smooth - lo) / jnp.maximum(hi - lo, 1e-9), 0.0, 1.0)
|
| 113 |
|
| 114 |
|
| 115 |
-
def generate(key: jax.Array, cfg: TerrainConfig = DEFAULT_CONFIG
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
k_reg, k_depth, k_stiff, k_dens, k_wet, k_void = jax.random.split(key, 6)
|
| 118 |
|
| 119 |
u = jax.random.uniform(k_reg, ())
|
| 120 |
regime = (u >= REGIME_EDGES[0]).astype(jnp.int32) + (u >= REGIME_EDGES[1]).astype(jnp.int32)
|
| 121 |
lows = jnp.array([r[0] for r in REGIME_DEPTH_RANGE_M])
|
| 122 |
highs = jnp.array([r[1] for r in REGIME_DEPTH_RANGE_M])
|
| 123 |
-
depth =
|
|
|
|
|
|
|
| 124 |
|
| 125 |
|
| 126 |
# Stiffness is COUPLED TO DEPTH, not independent of it. An earlier version drew the two
|
|
@@ -217,5 +227,5 @@ def sample(field: SnowField, cfg: TerrainConfig, xy_m: jax.Array) -> tuple[jax.A
|
|
| 217 |
field.damping_ns_per_m, field.void))
|
| 218 |
|
| 219 |
|
| 220 |
-
batched_generate = jax.vmap(generate, in_axes=(0, None))
|
| 221 |
batched_sample = jax.vmap(sample, in_axes=(0, None, 0))
|
|
|
|
| 112 |
return jnp.clip((smooth - lo) / jnp.maximum(hi - lo, 1e-9), 0.0, 1.0)
|
| 113 |
|
| 114 |
|
| 115 |
+
def generate(key: jax.Array, cfg: TerrainConfig = DEFAULT_CONFIG,
|
| 116 |
+
depth_scale: float = 1.0) -> SnowField:
|
| 117 |
+
"""Generate one environment's snow field. vmap over keys for a batch.
|
| 118 |
+
|
| 119 |
+
depth_scale shrinks every regime's depth range, which is how the training curriculum
|
| 120 |
+
works. Measured, a policy warm-started from flat ground handles snow up to ~70 mm well
|
| 121 |
+
(60% velocity tracking, no falls) and collapses at ~200 mm (-13% tracking, falls in 85
|
| 122 |
+
steps). Starting at full difficulty it converges on the easy 70% of terrain and treats
|
| 123 |
+
the deep third as unsolvable noise. Ramping the ceiling gives it a reachable next step.
|
| 124 |
+
"""
|
| 125 |
k_reg, k_depth, k_stiff, k_dens, k_wet, k_void = jax.random.split(key, 6)
|
| 126 |
|
| 127 |
u = jax.random.uniform(k_reg, ())
|
| 128 |
regime = (u >= REGIME_EDGES[0]).astype(jnp.int32) + (u >= REGIME_EDGES[1]).astype(jnp.int32)
|
| 129 |
lows = jnp.array([r[0] for r in REGIME_DEPTH_RANGE_M])
|
| 130 |
highs = jnp.array([r[1] for r in REGIME_DEPTH_RANGE_M])
|
| 131 |
+
depth = depth_scale * (
|
| 132 |
+
lows[regime] + _correlated_unit_field(k_depth, cfg) * (highs[regime] - lows[regime])
|
| 133 |
+
)
|
| 134 |
|
| 135 |
|
| 136 |
# Stiffness is COUPLED TO DEPTH, not independent of it. An earlier version drew the two
|
|
|
|
| 227 |
field.damping_ns_per_m, field.void))
|
| 228 |
|
| 229 |
|
| 230 |
+
batched_generate = jax.vmap(generate, in_axes=(0, None, None))
|
| 231 |
batched_sample = jax.vmap(sample, in_axes=(0, None, 0))
|