Hard unilateral contact with PGS: removing contact regularization (R=0) still allows penetration to accumulate (normal relative velocity not driven to zero) #2994
Unanswered
kankanzheli
asked this question in
Asking for Help
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Intro
Hi MuJoCo team,
I’m trying to achieve a “hard” unilateral normal contact behavior using the built-in PGS solver: when contact is active, I want the relative normal velocity at the contact to be driven to zero (or ≥ 0) each step, so that penetration depth does not keep increasing over time (small overlap at first is acceptable, but the overlap should not grow further).
My target behavior
Unilateral constraint (push-only, no pull): normal contact force ≥ 0.
When contact is active, the solver should make relative normal velocity ~ 0 at the end of the step (or at least prevent continued closing).
Result: the penetration depth should not accumulate across steps.
Simulation setup
Solver: PGS
timestep = 0.002
Contact parameters on robot geoms (example):
condim="1"
solref="0 -500" (direct format, intended as B ≈ 1/dt)
solimp="1 1 0 0.005 2"
With the default MuJoCo solver behavior, contacts still appear “soft” to me (penetration can keep increasing).
What I modified in MuJoCo source
I patched engine_core_constraint.c mainly in these places:
Remove contact-row regularization in mj_makeImpedance
In mj_makeImpedance, for PGS contact rows (mjCNSTR_CONTACT_*), I force:
R[i] = 0 for contact rows (for PGS only)
I also skip/guard the friction-related R rescaling logic to avoid 0/0 issues when R=0.
Recompute D for contact rows from diag(A) in mj_projectConstraint
Because D = 1/R no longer works for contact rows when R=0, in mj_projectConstraint I set for PGS contact rows:
D[i] = 1 / max(eps, diag(A))
Where diag(A) is taken from the diagonal of the computed A = B*B' (in sparse path I use the diag index after building AR; for contact rows R=0 so diag(AR)=diag(A)).
I kept the rest of the pipeline mostly unchanged:
KBIP computation stays the same (I did not fully redesign aref/K/B logic beyond using solref as usual).
Contact constraints remain unilateral via the existing projection logic (force >= 0).
I added a debug print to confirm the patched code is active, e.g.:
[OHYA_PATCH] mj_makeImpedance: PGS contact row hit ...
Current observed behavior (problem)
After these changes:
Contacts do generate pushing forces and objects can separate (so contact is not “dead”).
However, penetration depth can still keep increasing over time in some cases.
This suggests that the solver is not actually driving the relative normal velocity to 0 sufficiently, even with solref="0 -500" and small timestep.
My setup
mujoco 3.3.7
My question
With PGS, is it expected that even with strong velocity feedback (B ≈ 1/dt) the solver may still allow penetration to accumulate unless iterations are very high?
Does removing R for contact rows fundamentally break assumptions of MuJoCo’s PGS update / stopping criteria (tolerance/cost), causing early termination before the normal velocity is sufficiently corrected?
If the goal is “hard” unilateral contact (no additional penetration accumulation), what is the recommended approach in MuJoCo?
Is there a correct way to set R=0 (or near 0) and compute D so that PGS can converge reliably?
Are there internal solver parameters or logic (e.g., iteration limit / tolerance / scaling) that must be adjusted when R=0?
Is there an official/accepted method to emulate “v_n → 0” per step without moving to a custom plugin solver?
If helpful, I can provide:
A minimal XML and model to reproduce the penetration accumulation,
A clean patch/PR (diff) with the exact changes above,
Logs for solver_niter, contact efc_vel (normal), and penetration depth.
Minimal model and/or code that explain my question
No response
Confirmations
Beta Was this translation helpful? Give feedback.
All reactions