Skip to content

Regularized friction never sticks: a pinched capsule at 20–95× Coulomb static margin creeps through the grip at 50–2300 µm/s #3328

Description

@satesare603-bot

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:

import numpy as np
import mujoco

MJCF_TEMPLATE = """
<mujoco model="friction_vertical_leak">
  <option timestep="0.002" gravity="0 0 -9.81" cone="{cone}" impratio="{impratio}"
          noslip_iterations="{noslip}"/>
  <worldbody>
    <body name="padL" pos="-0.0125 0 0">
      <joint name="jL" type="slide" axis="1 0 0" damping="100"/>
      <geom name="gL" type="box" size="0.008 0.030 0.020" mass="0.2"
            friction="0.7 0.005 0.0001" condim="{condim}" {solref_attr}/>
    </body>
    <body name="padR" pos="0.0125 0 0">
      <joint name="jR" type="slide" axis="1 0 0" damping="100"/>
      <geom name="gR" type="box" size="0.008 0.030 0.020" mass="0.2"
            friction="0.7 0.005 0.0001" condim="{condim}" {solref_attr}/>
    </body>
    <body name="rod" pos="0 0 0">
      <freejoint/>
      <geom name="grod" type="capsule" fromto="0 -0.05 0 0 0.05 0" size="0.004" mass="0.045"
            friction="0.7 0.005 0.0001" condim="{condim}" {solref_attr}/>
    </body>
  </worldbody>
  <actuator>
    <position name="aL" joint="jL" kp="20000" ctrlrange="-0.02 0.02"/>
    <position name="aR" joint="jR" kp="20000" ctrlrange="-0.02 0.02"/>
  </actuator>
</mujoco>
"""

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)

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions