feat(randomization): add mean_matched_uniform distribution kind - #203
Open
tkevinbest wants to merge 2 commits into
Open
tkevinbest wants to merge 2 commits into
tkevinbest wants to merge 2 commits into
Conversation
A bare [lo, hi] range is uniform, so its mean is the band's midpoint. On a band that is asymmetric about the nominal value that biases every env: a link mass scale of [0.9, 1.2] averages 1.05, making every randomized link 5% heavier than its URDF, and an added-mass range of [-1.0, 3.0] puts +1.0 kg on every torso. The asymmetry is deliberate (the wide side covers payload), so rather than change the bounds this adds a distribution whose shape spans the same band with its expectation on the nominal. mean_matched_uniform is a two-piece uniform: with lo < m < hi, it draws U[lo, m] with probability p = (hi - m) / (hi - lo) and U[m, hi] otherwise, which is the lever rule and makes E[X] = m exactly. Its inverse CDF is piecewise linear with a kink at (p, m), so it drops into the existing inverse-CDF sampler; _inverse_cdf is the single spec.kind dispatch point in the repo, so no backend code changes. Also adds DistributionSpec.expectation(), the exact analytic mean for every kind, so a range's bias is computable rather than eyeballed. No existing kind can span an asymmetric band unbiased: on [0.9, 1.2] uniform gives 1.05, log_uniform 1.0428, and a gaussian with an explicit mean=1.0 still lands at 1.0028, since that mean is the pre-truncation one. Mechanism only -- no shipped preset changes, since flipping one alters training dynamics at a fixed seed and is a separate decision.
The docstring edits in randomization/terms/locomotion.py and objects.py were rewrites of existing prose in files this branch leaves functionally alone, so they are reverted. What remains is limited to the two modules that implement the new kind, plus its tests and a README note. Also cuts the commentary that argued for the change rather than describing the code: coverage percentages, restatements of adjacent error messages, and docstring prose duplicated across surfaces. Net effect on the diff: 6 files -> 4, and 18 pre-existing lines touched -> 9 (the Distribution Literal, the Fields docstring block, and the two constants that had to move).
tkevinbest
marked this pull request as ready for review
September 14, 2026 16:20
tkevinbest
enabled auto-merge (squash)
September 14, 2026 16:21
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Strictly opt-in: no shipped preset uses the new kind, so every existing config draws bit-identical values.
Two mass randomization presets use plain uniform ranges.
link_mass_range: [0.9, 1.2]is a multiplicative scale on each randomized link, unperturbed at1.0.added_mass_range: [-1.0, 3.0]is an additive kg offset on the torso, unperturbed at0.0. A uniform's mean is the midpoint of its band, and neither band is centered on its unperturbed value:[0.9, 1.2]averages 1.05,[-1.0, 3.0]averages +1.0 kg. Averaged over environments, the policy trains against a robot whose links are 5% heavier than the URDF describes and whose torso carries an extra kilogram it does not have. A config diff shows none of this; the ranges look reasonable. The asymmetry is deliberate — the wide upper side covers carrying a payload — so narrowing or re-centering the bounds is not the fix.mean_matched_uniformkeeps the same bounds and puts the mean where it belongs. Forlow < mean < highit drawsU[low, mean]with probability(high - mean) / (high - low)andU[mean, high]otherwise, soE[X] = meanexactly while the support stays[low, high].No existing kind does this on an asymmetric band: on
[0.9, 1.2],uniformgives 1.05,log_uniform1.0428, andgaussianwithmean=1.0, std=0.05gives 1.0028, because that parameter is the pre-truncation mean.DistributionSpec.expectation()returns the closed-form mean of any spec, so a range's mean can be checked rather than assumed.What holds the default behavior fixed:
distribution.py,sampler.pyand their tests references the new kind.[lo, hi]pair still parses asuniform.uniform,log_uniformandgaussianbranches of_inverse_cdfare untouched. The only edit to shared code moves_INV_SQRT2and_P_EPSfromsampler.pyintodistribution.pyat identical values, soexpectation()can share them.meanis required rather than defaulted to the midpoint, and validation requires strictlow < mean < high. A mean at the midpoint routes to the plain-uniform path so the two agree bit-for-bit. The kind string is serialized into checkpoints andDistributionSpec.parseraises on unknown kinds, so renaming it later breaks eval of checkpoints trained with it.98 unit tests (28 new),
no_simCI selection 551 passed / 9 skipped, pre-commit clean, IsaacSim DR matrix passing. IsaacGym and MuJoCo were not run and the kind has no cross-backend_dr_matrix.pycases, both because_inverse_cdfis the repo's onlyspec.kinddispatch.