google-deepmind / google-deepmind/mujoco
Regularized friction never sticks: a pinched capsule at 20–95× Coulomb static margin creeps through the grip at 50–2300 µm/s
- Dominant language
- C++
- Stars
- 15.2k
- Forks
- 1.8k
- Avg merge
- 10d 16h
- Merged PRs (30d)
- 25
Description
## Summary
With the documented grasping recipe (`cone="elliptic" impratio="10"`), a 45 g capsule pinched
between two μ=0.7 pads at a measured normal load of 12–60 N — Coulomb static capacity 20–95×
the 0.44 N gravity load — does **not** hang in place: it slips downward through the grip at a
**steady ~50–155 µm/s** (and ~1500–2300 µm/s at `impratio="1"` / pyramidal). The slip is pure
tangential creep (a rotation discriminator rules out rolling; the pads are joint-locked in z),
it scales ≈ linearly with `1/impratio`, and `noslip_iterations="10"` reduces it ~110× (to
1.4 µm/s). In other words, the regularized friction model appears to have **no static-friction
stick state**: any constant tangential load far inside the friction cone produces a steady
drift. For grasp-and-hold scenarios this means friction-held objects sink out of grippers on
robot timescales (155 µm/s ≈ 9 mm/min at the documented recipe).
## Environment
- mujoco **3.8.1** (PyPI `mujoco`), numpy, Python 3.12, Linux x86_64, CPU
- Pure stock MuJoCo — no wrappers, no other dependencies
## Minimal repro
Two pad boxes on damped x-slides squeeze a horizontal free capsule (r=4 mm, m=45 g) 2 mm past
first contact via position actuators; gravity pulls the capsule down with W = 0.44 N; the
capsule's z is tracked for 2 s after a 0.5 s settle:
```python
import numpy as np
import mujoco
MJCF_TEMPLATE = """
"""
SETTLE_S, MEASURE_S, SQUEEZE_M = 0.5, 2.0, 0.002
def run_case(cone="elliptic", impratio=10, condim=3, solref=None, noslip=0):
solref_attr = f'solref="{solref[0]} {solref[1]}"' if solref else ""
xml = MJCF_TEMPLATE.format(cone=cone, impratio=impratio, condim=condim,
solref_attr=solref_attr, noslip=noslip)
m = mujoco.MjModel.from_xml_string(xml)
d = mujoco.MjData(m)
d.ctrl[0] = +(0.0005 + SQUEEZE_M)
d.ctrl[1] = -(0.0005 + SQUEEZE_M)
for _ in range(int(SETTLE_S / m.opt.timestep)):
mujoco.mj_step(m, d)
z0 = float(d.qpos[2 + 2])
f6, N_tot = np.zeros(6), 0.0
for ci in range(d.ncon):
g1, g2 = d.contact.geom[ci]
names = {mujoco.mj_id2name(m, mujoco.mjtObj.mjOBJ_GEOM, int(g1)),
mujoco.mj_id2name(m, mujoco.mjtObj.mjOBJ_GEOM, int(g2))}
if "grod" in names:
mujoco.mj_contactForce(m, d, ci, f6)
N_tot += abs(float(f6[0]))
q_start = d.qpos[2 + 3:2 + 7].copy()
n_meas = int(MEASURE_S / m.opt.timestep)
for _ in range(n_meas):
mujoco.mj_step(m, d)
drop_um = (z0 - float(d.qpos[2 + 2])) * 1e6
rot = 2 * float(np.arccos(min(1.0, abs(float(np.dot(q_start, d.qpos[2 + 3:2 + 7]))))))
print(f"{cone:9} imp={impratio:<3} condim={condim} solref={solref or 'default'} "
f"noslip={noslip}: N={N_tot:5.1f} N (margin {0.7 * N_tot / 0.441:5.1f}x) "
f"drop={drop_um:7.1f} um/2s ({drop_um / MEASURE_S:7.2f} um/s) "
f"roll-equiv={rot * 0.004 * 1e6:5.1f} um")
for kw in (dict(cone="elliptic", impratio=10), dict(cone="elliptic", impratio=1),
dict(cone="elliptic", impratio=100), dict(cone="pyramidal", impratio=1),
dict(cone="elliptic", impratio=10, condim=6),
dict(cone="elliptic", impratio=10, solref=(0.002, 1)),
dict(cone="elliptic", impratio=10, noslip=10)):
run_case(**kw)
```
## Observed (mujoco 3.8.1)
```text
cone imp cd solref nslip | N[N] marg | drop[um/2s] um/s um/step | rot[mrad] rollequiv
elliptic 10 3 default(0.02,1) 0 | 12.4 19.7 | 310.0 154.99 0.31 | 0.47 1.9
elliptic 1 3 default(0.02,1) 0 | 12.4 19.7 | 3103.6 1551.79 3.1036 | 5.06 20.2
elliptic 100 3 default(0.02,1) 0 | 12.4 19.7 | 30.7 15.33 0.0307 | 0.11 0.4
pyramidal 1 3 default(0.02,1) 0 | 26.8 42.5 | 4602.5 2301.23 4.6025 | 6.39 25.6
elliptic 10 6 default(0.02,1) 0 | 12.4 19.7 | 307.0 153.48 0.307 | 0.35 1.4
elliptic 10 3 (0.002, 1) 0 | 59.7 94.6 | 102.6 51.32 0.1026 | 0.53 2.1
elliptic 10 3 default(0.02,1) 10 | 12.4 19.7 | 2.8 1.41 0.0028 | 0.42 1.7
```
(The last row: `noslip_iterations=10` cuts the creep ~110×, 155 → 1.41 µm/s.)
## Expected
Coulomb statics: at margins of 20–95× the load, zero steady-state slip — the capsule hangs.
## Analysis (empirical characterization)
- **Pure tangential creep, not rolling and not pad motion**: the rod's total rotation over the
window is 0.1–6.4 mrad → a roll-equivalent displacement of 0.4–26 µm, < 1 % of the observed
drops; the pads are slide-joint-locked in z (verified zero z motion).
- **Rate ∝ 1/impratio** (1552 → 155 → 15.3 µm/s for impratio 1 → 10 → 100 at identical N):
consistent with the tangential regularization stiffness scaling.
- `condim=6` (adds rolling/torsional rows): no effect on the vertical creep (310 → 307 µm/2s) —
as expected for a non-rolling mode.
- Stiffer `solref=(0.002,1)`: N rises 12.4 → 59.7 N at the same squeeze; the creep improves only
~3× (155 → 51 µm/s) while the Coulomb margin rose to 95× — the leak does not close with
contact stiffness.
- `noslip_iterations=10`: 110× reduction (155 → 1.4 µm/s) — the post-pass appears to be the
one switch that approximates a stick state.
- The behavior looks inherent to the regularized friction solution (a finite tangential-force ↔
slip-velocity mapping near the origin), not an integration artifact: the rate is steady over
the window and timestep-consistent per-step values are listed.
## Why this matters
The documentation recommends `cone="elliptic"` with elevated `impratio` for grasping. At that
recipe, any friction-held object under constant gravity drifts ~9 mm/min through a firm pinch
(and faster through softer/lower-impratio contacts). For manipulation tasks that hold objects
for seconds-to-minutes (pick-and-place, cable routing, insertion), friction grasps measurably
leak even at high normal loads, which is easy to misattribute to controller or model errors.
## Questions / asks (suggestion tone)
1. Is this steady small-load creep the **expected/intended** behavior of the regularized
friction model? If so, a docs note in the grasping guidance would save users a long
diagnosis (we can draft one).
2. Is `noslip_iterations` the **recommended** remedy for grasp-and-hold (and what are its
known costs/caveats at scale)?
3. Is there guidance for sizing `impratio` against the expected load ratio (creep budget per
our table ≈ 1550/impratio µm/s at default solref)?
4. Would a true stick state / velocity deadband option be in scope for the solver roadmap?
We are happy to contribute the repro as a test case or a documentation PR if any of these are
useful.
Contributor guide
Assessment
This issue has not been assessed yet.