Core concept

Coulomb’s law says a contact can resist a tangential force up to $\mu$ times its normal force; below that it sticks, at that limit it slides. MuJoCo implements friction inside the same soft-constraint machinery as the normal force (Lesson 9.2), which has three consequences you can measure:

[!established] Friction in MuJoCo’s documentation Friction rows of elliptic cones and friction loss have zero position residual ($r \equiv 0$): their stiffness is zero, their impedance is $d_0$ (solimp[0]), and they act as pure dampers with $b = 2/(d_w\,\text{timeconst})$. impratio (elliptic cones) makes friction rows “harder” than normal ones; it reduces slow slippage “without increasing the actual friction coefficient or guaranteeing exact sticking”. noslip_iterations adds a post-processing pass that removes most remaining slip, at extra cost and with inverse dynamics becoming ill-defined. Pyramidal cones mix normal and frictional directions in each basis vector, and high impratio is not recommended with them.

Visual intuition

  1. With the elliptic cone and $\mu$ = 0.4, raise the slope from 15° towards 22°. Near 21.8° ($\tan^{-1}0.4$) the block breaks away; below it, it creeps at under a millimetre per second, and the readout predicts how fast.
  2. Set the slope back to 15°, then set impratio to 10: the creep drops tenfold, as the formula says.
  3. Switch to the pyramidal cone: the creep nearly doubles, and the block breaks away earlier than $\tan^{-1}\mu$.

```lab simlab {“dock”: true, “title”: “The incline test, and creep below it”, “model”: “incline”, “height”: 230, “camera”: {“azimuth”: -90, “elevation”: 8, “distance”: 1.4, “target”: [0, 0, 0.25]}, “setup”: “ctx.cone = model.opt.cone”, “controls”: [ {“label”: “slope angle”, “unit”: “deg”, “init”: true, “reset”: true, “min”: 0, “max”: 40, “step”: 0.25, “value”: 15, “digits”: 2, “apply”: “const a = value * Math.PI / 180; data.mocap_quat.set([Math.cos(a / 2), 0, Math.sin(a / 2), 0], 0); data.qpos.set([data.mocap_pos[0] + 0.04 * Math.sin(a), data.mocap_pos[1], data.mocap_pos[2] + 0.04 * Math.cos(a), Math.cos(a / 2), 0, Math.sin(a / 2), 0], 0); data.qvel.fill(0); ctx.angle = a”}, {“label”: “friction coefficient (ramp and block)”, “apply”: “model.geom_friction[3] = value; model.geom_friction[6] = value; ctx.mu = value”, “min”: 0.1, “max”: 1.0, “step”: 0.01, “value”: 0.4, “digits”: 2}, {“type”: “select”, “label”: “friction cone”, “set”: “model.opt.cone”, “options”: [[“elliptic (as modelled)”, 1], [“pyramidal”, 0]], “value”: 1, “reset”: true}, {“type”: “select”, “label”: “impratio”, “set”: “model.opt.impratio”, “options”: [[“1”, 1], [“10”, 10]], “value”: 1, “reset”: true}], “readouts”: [ {“label”: “Coulomb says”, “expr”: “Math.tan(ctx.angle || 0) > ctx.mu ? ‘slides’ : ‘holds’”}, {“label”: “speed along slope (mm/s)”, “expr”: “1000 * Math.hypot(data.qvel[0], data.qvel[1], data.qvel[2])”, “digits”: 4}, {“label”: “creep predicted, elliptic (mm/s)”, “expr”: “model.opt.cone !== 1 || Math.tan(ctx.angle || 0) > ctx.mu ? ‘-‘ : 1000 * (0.1 / 0.9) * 9.81 * Math.sin(ctx.angle) * (0.95 * 0.02 / 2) / (4 * model.opt.impratio)”, “digits”: 4}], “plots”: [{“ylabel”: “speed (mm/s)”, “window”: 5, “traces”: [{“label”: “speed along slope (mm/s)”, “expr”: “1000 * Math.hypot(data.qvel[0], data.qvel[1], data.qvel[2])”}]}], “note”: “Changing the angle re-places the block at rest. The prediction is the steady creep of a block on four contacts with the default solref and solimp; give the block a second or two to settle into it.”}


## Mathematics

### The slip threshold

On a slope of angle $\theta$, gravity's tangential component is $mg\sin\theta$ and the normal force $mg\cos\theta$. The contact holds while $mg\sin\theta \le \mu\,mg\cos\theta$, that is $\tan\theta \le \mu$, whatever the mass. Output (1) tilts the ramp slowly and records when the block starts moving faster than 1 cm/s: with elliptic cones, within 0.15° of $\tan^{-1}\mu$ for three coefficients. With pyramidal cones the block goes early, by 0.4° at $\mu$ = 0.3 and 3° at $\mu$ = 0.8: its effective coefficient is about 90% of $\mu$ at the higher values (a measured property of this setup, consistent with the documentation's warning that pyramidal cones mix normal and friction directions).

### Why friction creeps, and how fast

A friction row has no position residual, so its reference acceleration is $a_\text{ref} = -b\,v$. For one row with $\hat A = A$, the constrained acceleration interpolates as in Lesson 9.2, $a_1 = d\,a_\text{ref} + (1-d)\,a_0$. A steady creep has $a_1 = 0$, so

$$v = \frac{1-d_0}{d_0}\,\frac{a_0}{b} = \frac{1-d_0}{d_0}\;a_0\;\frac{d_w\,\text{timeconst}}{2}.$$

> [!derivation] Four contacts, and impratio
> The block rests on four contacts that share one translation, so each friction row sees a quarter of the coupling and the creep is divided by the number of contacts carrying the load. `impratio` divides the friction rows' regularizer: MuJoCo's own `efc_R` (output 2) shows the friction rows at exactly $R_\text{normal}/\text{impratio}$. Altogether, for a block on a slope,
> $$v_\text{creep} = \frac{1-d_0}{d_0}\;g\sin\theta\;\frac{d_w\,\text{timeconst}}{2}\;\frac{1}{n_\text{contacts}\;\text{impratio}}.$$
> At 15° with the defaults: $0.111 \times 2.539 \times 0.0095 / 4$ = 0.67 mm/s. Output (2) measures 0.661, and the formula tracks every change: tenfold less with `impratio` 10, half with half the time constant, about an eleventh with $d_0$ = 0.99.

The formula says what controls creep: impedance (`solimp[0]`), the time constant, `impratio`, and the load's share per contact. It also says what does not: the friction coefficient, as long as the load is inside the cone. NoSlip removes the creep almost entirely (output 2), and the pyramidal cone creeps faster than the elliptic formula, by a factor of about 1.8 here.

### Choosing condim

- `condim` 1: frictionless. The ball in output (3) slides down a 10° slope at exactly $g\sin 10°$.
- `condim` 3: sliding friction. The same ball rolls without slipping at $\tfrac57 g\sin 10°$, the rolling solution for a solid sphere (measured 1.2142 against 1.2168 m/s after 1 s).
- `condim` 4: adds torsional friction, a torque about the normal limited by `friction[1]` (a length) times the normal force. A ball spinning in place on one contact point keeps spinning at 20 rad/s with `condim` 3 and stops within a second with `condim` 4. Objects held by a single fingertip need it; flat faces with several contacts get most of their torsional resistance from sliding friction at separate points (Lesson 5.3).
- `condim` 6: adds rolling friction, a torque about the tangent axes limited by `friction[2]` times the normal force. With 0.02 m, more than $r\tan 10°$ = 0.0088 m, the ball does not move at all.

> [!recommendation] Defaults for manipulation
> Elliptic cones with `impratio` of 10 or more when grasping matters (`gantry_gripper.xml` uses 10); `condim` 4 on fingertips and pads, `condim` 3 elsewhere; `condim` 6 only for objects that must not roll unrealistically; NoSlip only when creep over the task's duration exceeds your tolerance and you do not need inverse dynamics. Estimate the creep with the formula above before deciding.

## Implementation

```io
INPUT: incline.xml; small scenes in the script
PROCESS: find the slip angle for three friction coefficients and both cones; measure creep below it for six settings against the formula; demonstrate condim 1, 3, 4 and 6
OUTPUT: printed tables

```python file=examples/l9_3_friction.py “"”Lesson 9.3: friction: the incline test, slow creep, condim, and the two friction cones.

INPUT incline.xml (a 0.2 kg block on a ramp that is a mocap body); small scenes below PROCESS (1) tilt the ramp slowly and find the angle at which the block starts to slide, for three friction coefficients and both cones, against atan(mu); (2) below the slip angle: the creep velocity against the prediction from the friction rows’ soft-constraint equation, for five parameter settings, and the regularizer of friction rows against the normal row’s; (3) condim: a ball on a 10 degree slope with no friction, sliding friction only, and rolling friction; a ball spinning in place with and without torsional friction OUTPUT printed tables

Run: python examples/l9_3_friction.py “””

import math import os

import mujoco import numpy as np

from mjcourse import model_path

G = 9.81 FAST = os.environ.get(“MJC_FAST”) == “1”

def incline(mu: float, cone: str = “mjCONE_ELLIPTIC”, impratio: float = 1.0, tc: float = 0.02, d0: float = 0.9, noslip: int = 0) -> tuple[mujoco.MjModel, mujoco.MjData]: model = mujoco.MjModel.from_xml_path(str(model_path(“incline”))) model.opt.cone = getattr(mujoco.mjtCone, cone) model.opt.impratio, model.opt.noslip_iterations = impratio, noslip for g in range(model.ngeom): # same parameters on every geom: no mixing model.geom_friction[g][0] = mu model.geom_solref[g] = [tc, 1.0] model.geom_solimp[g][0] = d0 return model, mujoco.MjData(model)

def place(model, data, angle: float) -> np.ndarray: “"”Tilt the ramp about world y, rest the block on it; return the downhill unit vector.””” quat = np.array([math.cos(angle / 2), 0.0, math.sin(angle / 2), 0.0]) data.mocap_quat[0] = quat rot = np.zeros(9) mujoco.mju_quat2Mat(rot, quat) rot = rot.reshape(3, 3) data.qpos[0:3] = data.mocap_pos[0] + rot[:, 2] * (0.01 + 0.03) data.qpos[3:7] = quat data.qvel[:] = 0 mujoco.mj_forward(model, data) return -rot[:, 0] if rot[2, 0] > 0 else rot[:, 0]

def slip_angle(mu: float, cone: str) -> float: “"”Raise the tilt by 0.05 degree per 0.25 s and return the angle where the block moves faster than 1 cm/s.””” model, data = incline(mu, cone) start = math.degrees(math.atan(mu)) - (1.0 if FAST else 3.0) angle = start place(model, data, math.radians(angle)) while angle < 60: angle += 0.05 quat = np.array([math.cos(math.radians(angle) / 2), 0.0, math.sin(math.radians(angle) / 2), 0.0]) data.mocap_quat[0] = quat for _ in range(125): mujoco.mj_step(model, data) if np.linalg.norm(data.qvel[0:3]) > 0.01: return angle return float(“nan”)

def creep(**kw) -> tuple[float, int, np.ndarray]: model, data = incline(0.4, **kw) down = place(model, data, math.radians(15)) for _ in range(1000): # settle 2 s mujoco.mj_step(model, data) p0, t0 = data.qpos[0:3].copy(), data.time for _ in range(5000): # measure over 10 s mujoco.mj_step(model, data) return float(np.dot(data.qpos[0:3] - p0, down) / (data.time - t0)), data.ncon, np.array(data.efc_R[:4])

def condim_demos() -> None: # MJCF angles are in degrees unless says otherwise. slope = “”“ <body euler="0 10 0"></body> <body pos="0 0 0.0508"></body> ””” a = G * math.sin(math.radians(10)) for condim, roll, note in ((1, 0.0001, f”frictionless sliding predicts g sin 10 deg x 1 s = {a:.4f}”), (3, 0.0001, f”rolling without slip predicts 5/7 g sin 10 deg x 1 s = {5 / 7 * a:.4f}”), (6, 0.02, “rolling friction 0.02 m exceeds r tan 10 deg = 0.0088 m: no motion”)): model = mujoco.MjModel.from_xml_string(slope.format(condim=condim, roll=roll)) data = mujoco.MjData(model) for _ in range(100): # settle onto the surface mujoco.mj_step(model, data) v0 = np.linalg.norm(data.qvel[0:3]) for _ in range(500): mujoco.mj_step(model, data) speed = np.linalg.norm(data.qvel[0:3]) - v0 print(f” ball on a 10 degree slope, condim {condim}: speed gained in 1 s {speed:.4f} m/s ({note})”) spin = “”“ <body pos="0 0 0.05"></body> ””” for condim in (3, 4): model = mujoco.MjModel.from_xml_string(spin.format(condim=condim)) data = mujoco.MjData(model) data.qvel[5] = 20.0 # spin about the vertical at 20 rad/s, one contact point for _ in range(500): mujoco.mj_step(model, data) print(f” ball spinning about the vertical at 20 rad/s, condim {condim}: spin after 1 s {data.qvel[5]:.3f} rad/s”)

if name == “main”: print(“(1) slip angle: tilt slowly until the block moves faster than 1 cm/s”) print(f” {‘mu’:>5}{‘atan(mu) (deg)’:>16}{‘elliptic (deg)’:>16}{‘pyramidal (deg)’:>17}”) for mu in (0.3, 0.5, 0.8): print(f” {mu:>5}{math.degrees(math.atan(mu)):>16.2f}{slip_angle(mu, ‘mjCONE_ELLIPTIC’):>16.2f}” f”{slip_angle(mu, ‘mjCONE_PYRAMIDAL’):>17.2f}”) print(“(2) creep on a 15 degree slope with mu = 0.4, below the 21.8 degree slip angle”) print(“ prediction for elliptic cones: v = (1 - d0)/d0 * g sin(15 deg) * (dw tc / 2) / (contacts * impratio)”) a0 = G * math.sin(math.radians(15)) for label, kw in ((“elliptic, defaults”, {}), (“elliptic, impratio 10”, {“impratio”: 10.0}), (“elliptic, timeconst 0.01”, {“tc”: 0.01}), (“elliptic, d0 0.99”, {“d0”: 0.99}), (“elliptic, impratio 10, noslip 3”, {“impratio”: 10.0, “noslip”: 3}), (“pyramidal, defaults”, {“cone”: “mjCONE_PYRAMIDAL”})): v, n, r = creep(**kw) d0, tc, imp = kw.get(“d0”, 0.9), kw.get(“tc”, 0.02), kw.get(“impratio”, 1.0) pred = (1 - d0) / d0 * a0 * (0.95 * tc / 2) / (n * imp) shown = f”{1000 * pred:.4f}” if “pyramidal” not in label and “noslip” not in label else “-“ print(f” {label:<34} creep {1000 * v:8.4f} mm/s predicted {shown:>7} R of first contact’s rows {np.round(r[:3], 5)}”) print(“(3) condim”) condim_demos()

Output:

```text
(1) slip angle: tilt slowly until the block moves faster than 1 cm/s
     mu  atan(mu) (deg)  elliptic (deg)  pyramidal (deg)
    0.3           16.70           16.85            16.30
    0.5           26.57           26.67            24.42
    0.8           38.66           38.76            35.71
(2) creep on a 15 degree slope with mu = 0.4, below the 21.8 degree slip angle
  prediction for elliptic cones: v = (1 - d0)/d0 * g sin(15 deg) * (dw tc / 2) / (contacts * impratio)
  elliptic, defaults                 creep   0.6614 mm/s   predicted  0.6700   R of first contact's rows [0.54496 0.54496 0.54496]
  elliptic, impratio 10              creep   0.0661 mm/s   predicted  0.0670   R of first contact's rows [0.54496 0.0545  0.0545 ]
  elliptic, timeconst 0.01           creep   0.3347 mm/s   predicted  0.3350   R of first contact's rows [0.55487 0.55487 0.55487]
  elliptic, d0 0.99                  creep   0.0610 mm/s   predicted  0.0609   R of first contact's rows [0.05056 0.05056 0.05056]
  elliptic, impratio 10, noslip 3    creep  -0.0001 mm/s   predicted       -   R of first contact's rows [0.5447  0.05447 0.05447]
  pyramidal, defaults                creep   1.1713 mm/s   predicted       -   R of first contact's rows [0.2062 0.2062 0.2062]
(3) condim
  ball on a 10 degree slope, condim 1: speed gained in 1 s 1.7035 m/s (frictionless sliding predicts g sin 10 deg x 1 s = 1.7035)
  ball on a 10 degree slope, condim 3: speed gained in 1 s 1.2142 m/s (rolling without slip predicts 5/7 g sin 10 deg x 1 s = 1.2168)
  ball on a 10 degree slope, condim 6: speed gained in 1 s 0.0000 m/s (rolling friction 0.02 m exceeds r tan 10 deg = 0.0088 m: no motion)
  ball spinning about the vertical at 20 rad/s, condim 3: spin after 1 s 20.000 rad/s
  ball spinning about the vertical at 20 rad/s, condim 4: spin after 1 s 0.000 rad/s

Debugging

A block slides on a slope it should hold. The cone is pyramidal and $\mu$ is near the threshold (output 1), or the load is shared by fewer contacts than you think. Switch to elliptic cones and check $\tan\theta$ against the contact’s actual coefficient (data.contact[i].friction, after mixing).

A held object slowly slides out over seconds. Creep. Compute $v_\text{creep}$ from the formula and multiply by the hold time; if it exceeds your tolerance, raise impratio, raise $d_0$, or enable NoSlip.

A ball on a slope rolls far too slowly or not at all. Rolling friction (condim 6 with friction[2] too large), or the slope is not the angle you think. While writing this lesson’s experiment, the slope was written as euler="0 0.1745 0", meaning 10° in radians, in a model without <compiler angle="radian"/>. MJCF reads angles in degrees by default, so the slope was 0.17° and the ball crept at a sixtieth of the expected acceleration. It is the Debugging clinic’s case 3, and it happens to authors too.

A spinning object on a single contact never stops. condim 3 has no torsional friction; use 4.

Exercise

A 0.5 kg box rests on a 20° slope with $\mu$ = 0.6 on its four corner contacts, elliptic cones, default solref and solimp, impratio 1. Predict the creep velocity, then the time for it to slide 1 cm. Check in the playground, then find the smallest impratio that keeps the slide under 1 mm in 10 s.

Challenge

Derive the creep velocity for a pyramidal cone from the soft-constraint equations (each pyramid edge is one row with its own $R$), predict output (2)’s pyramidal value, and explain why the pyramidal threshold in output (1) falls below $\tan^{-1}\mu$ as $\mu$ grows.

Research connection

[!research] Friction is the least certain parameter in most simulations Real friction coefficients vary with material pairs, surface condition, speed and load, typically by tens of percent, which is why friction is a standard target of domain randomization (Level 17). This lesson adds two simulator-side effects to that physical uncertainty: the cone type changes the effective threshold, and the soft model adds creep governed by parameters unrelated to $\mu$. A policy that relies on a grasp holding for many seconds is sensitive to all three (inference from outputs 1 and 2 and Lesson 5.3).

{"id": "9.3-check", "title": "Knowledge check", "questions": [
  {"kind": "numeric", "q": "A block rests on a slope with friction coefficient 0.6 (elliptic cones). At what angle (degrees) does Coulomb's law predict it starts to slide?",
   "answer": 30.96, "tol": 0.05, "unit": "deg",
   "explain": "<p>$\\tan^{-1} 0.6$ = 30.96°, independent of the mass.</p>"},
  {"kind": "numeric", "q": "With the default solref and solimp (d0 = 0.9, dw = 0.95, timeconst 0.02 s), a block on four contacts sits on a 15° slope below its slip angle, elliptic cones, impratio 1. What creep velocity (mm/s) does the formula give? (g = 9.81 m/s²)",
   "answer": 0.67, "tol": 0.01, "unit": "mm/s",
   "explain": "<p>$\\frac{0.1}{0.9} \\times 9.81 \\sin 15° \\times \\frac{0.95 \\times 0.02}{2} / 4$ = 0.67 mm/s; output (2) measures 0.661.</p>"},
  {"kind": "mcq", "q": "Which change does NOT reduce the creep of a block held below its slip angle with elliptic cones?",
   "options": ["Raising impratio", "Raising solimp's d0", "Raising the friction coefficient μ", "Lowering the time constant"],
   "answer": 2,
   "explain": "<p>Inside the cone, μ does not appear in the creep formula: the friction rows act as dampers whose strength is set by the impedance, time constant and impratio.</p>"},
  {"kind": "mcq", "q": "A ball spins in place on a table on one contact point. Which condim makes it stop?",
   "options": ["1", "3", "4", "None: spinning in place cannot be stopped by friction"],
   "answer": 2,
   "explain": "<p>Torsional friction, about the contact normal, comes with condim 4 (and 6). With condim 3 the spin continues unchanged (output 3).</p>"}
]}

Next

Lesson 9.4 asks why simulations explode, collects the ways this course has seen them do it, and orders the fixes by cost.