Skip to content

Add the geometric term to the flex stretch stiffness - #3451

Merged
copybara-service[bot] merged 1 commit into
google-deepmind:mainfrom
smallquail:flex-geometric-stiffness
Aug 4, 2026
Merged

Add the geometric term to the flex stretch stiffness#3451
copybara-service[bot] merged 1 commit into
google-deepmind:mainfrom
smallquail:flex-geometric-stiffness

Conversation

@smallquail

Copy link
Copy Markdown
Contributor

K_stretch was the Gauss-Newton Hessian of the stretch force rather than its Jacobian. With elongation e_a = L_a^2 - L0_a^2 and force f = -sum_ab M_ab e_a grad(e_b)/2, the Hessian is

K = 2 sum_ab M_ab (s_a d_a)(s_b d_b)^T  +  sum_a Me_a (Laplacian_a (x) I3)

and only the first term was present. The second is proportional to the edge tension Me_a = sum_b M_ab e_b, so it vanishes at rest and grows with strain — the operator was first-order correct and no more.

Finite-differencing against -d(qfrc_passive)/dq on a mesh dilated by 5% gives an error of 7.8% of the force scale; with the geometric term it is exact to roundoff. The existing FlexStretchDerivatives did not catch this because it deforms at 5e-4 strain, where the two agree — its comments documented the omission as intended behaviour.

Only the tensile part is added. The geometric block is Me_a*[[I,-I],[-I,I]] over the edge's two vertices, which is positive semi-definite exactly when Me_a >= 0. A compressed edge would make K indefinite, and both consumers — the CG constraint solver and the PCG in mjd_effSolve — require an SPD operator. Because the sign condition is structural, the projection is a max(Me, 0) and needs no eigendecomposition. It is confined to the operator: mj_flexPassiveStretch keeps the full Me_a, so no force changes.

Both the matrix-free operator and the CSR assembly that the effective metric builds from are updated, since they must agree.

Impact

This changes how flexes with elastic2d="stretch" integrate under the implicit integrators and the effective metric. model/flex/bag.xml moves; model/flex/poncho.xml is bit-identical, because bending energy is quadratic in position and has no geometric term at all. Models with no flex elasticity are untouched.

The interpolated-flex path still uses its own Gauss-Newton approximation, which FlexInterpDerivativesDeformed asserts explicitly; that is left alone.

Testing

  • New DerivativeTest.FlexStretchDerivativesTensile dilates the mesh so every edge is in tension (the clamp inactive, so the operator is exact) and finite-differences the operator against the force. It fails without this change at 7.02 vs a 0.009 tolerance.
  • Full suite: 1342/1342 pass.

K_stretch was the Gauss-Newton Hessian of the stretch force, not its Jacobian.
With elongation e_a = L_a^2 - L0_a^2 and force f = -sum_ab M_ab e_a grad(e_b)/2,

  K = 2 sum_ab M_ab (s_a d_a)(s_b d_b)^T + sum_a Me_a (Laplacian_a (x) I3)

and only the first term was there. The second is proportional to the edge
tension Me_a = sum_b M_ab e_b, so it vanishes at rest and grows with strain:
the operator was first-order correct and no more. Finite-differencing it
against -d(qfrc_passive)/dq on a mesh dilated by 5% gives 7.8% of the force
scale; with the term it is exact to roundoff.

Add only the tensile part. The geometric block is Me_a*[[I,-I],[-I,I]] over the
edge's two vertices, which is positive semi-definite exactly when Me_a >= 0; a
compressed edge would make K indefinite, and both consumers -- the CG
constraint solver and the PCG in mjd_effSolve -- require SPD. The clamp is
structural, so no eigendecomposition is needed, and it is confined to the
operator: mj_flexPassiveStretch keeps the full Me_a, so no force changes.

Both the matrix-free operator and the CSR assembly the effective metric builds
from are updated, since they must agree.

This changes how flexes with elastic2d="stretch" integrate under the implicit
integrators and the effective metric -- bag.xml moves, poncho.xml is
bit-identical because bending energy is quadratic and has no geometric term.
The interpolated-flex path still uses its Gauss-Newton approximation, which
FlexInterpDerivativesDeformed asserts.
@quagla
quagla requested a review from yuvaltassa August 3, 2026 15:17
smallquail added a commit to smallquail/mujoco that referenced this pull request Aug 3, 2026
K_stretch was the Gauss-Newton Hessian of the stretch force, not its Jacobian.
With elongation e_a = L_a^2 - L0_a^2 and force f = -sum_ab M_ab e_a grad(e_b)/2,

  K = 2 sum_ab M_ab (s_a d_a)(s_b d_b)^T + sum_a Me_a (Laplacian_a (x) I3)

and only the first term was there. The second is proportional to the edge
tension Me_a = sum_b M_ab e_b, so it vanishes at rest and grows with strain.
Only its tensile part is added: the geometric block is Me_a*[[I,-I],[-I,I]],
positive semi-definite exactly when Me_a >= 0, and its consumers need SPD. The
clamp is structural, so no eigendecomposition is needed, and the force keeps
the full Me_a.

Carried here as its own commit because the FEM path below needs it: without it
the humanoid-in-bag scene diverges within 0.3 s. Duplicated from PR google-deepmind#3451
against upstream, so drop this commit when that lands.
@copybara-service
copybara-service Bot merged commit 10793e5 into google-deepmind:main Aug 4, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants