Expand description
Trajectory adjoint through the convex contact solve — the same contact model the forward simulator runs.
§Why this module exists
crate::rollout’s adjoint differentiates a self-contained per-vertex
ground-penalty contact model. The forward simulation path
(phyz::Simulator::step_with_contacts) does not use that model: it detects
ground contacts with a margin, assembles the convex problem
(phyz_contact::assemble), and solves it with the active-set Newton in
phyz_contact::convex under MuJoCo-style solref/solimp stabilization. A
gradient of the penalty model is a gradient of different physics than the
engine integrates — the third instance of the solver/gradient-disagreement
bug class in this codebase, and the largest.
This module closes it. convex_adjoint_gradient runs exactly the
forward step Simulator::step_with_contacts runs — same detection, same
assembly, same warm-started solve — caches each step’s phyz_contact::ContactProblem
and ContactSolution, and backpropagates through the implicit-function-
theorem sensitivities of the actual solve the forward pass performed
(phyz_contact::gradient::FixedPointSensitivity), at the regimes the
solver converged to.
§How the backward pass works
One contacted step is
qdd = ABA(q, v, u; π) (no contact forces)
v_free = v + dt·qdd
f* = argmin ½ fᵀ(A(q,π) + R(q,depth))f + fᵀ b(q,v_free) s.t. cone
v' = v_free + M⁻¹(q,π) Jᵀ(q) f*
q' = Φ(q, v') (integrate_configuration)Differentiating: dv' = dv_free + d(M⁻¹Jᵀ f*)|_{f fixed} + M⁻¹Jᵀ df*,
and by the IFT at the converged fixed point with the active set held
fixed, df* follows from the exact map linearization
(FixedPointSensitivity::apply): the differential of the stationarity
residual (A + R)f* + b − e_n·bias (impulses held fixed) on the sticking
rows and sliding normal rows, plus the sliding tangential channel
(slip-direction rotation) through each sliding contact’s own tangential
block. Separating contacts respond to nothing, by construction.
The smooth factors (ABA, kinematics, assembly, Φ) are differentiated by
central finite differences per lane — the same discipline
crate::semi_implicit_step_jacobians documents for its ABA block. The
contact solve itself is never finite-differenced: its derivative is the
exact IFT sensitivity of the converged solution, which is what makes the
gradient the derivative of the physics rather than of the solver
iterations. Within a lane the contact set is frozen to the identities
the nominal step detected (each contact is re-evaluated from its material
anchor on the body), so a lane never re-runs detection and cannot flip the
active set.
§Where the gradient is exact, one-sided, or undefined
- Interior of a regime (a contact firmly separated beyond the margin,
firmly loaded and sticking, or firmly sliding): the gradient is the exact
derivative of the regularized contact model, up to central-difference
error (
~1e-9relative) in the smooth blocks. - Contact activation: the margin band (see
ContactMaterial::impedance_at) ramps the impedance smoothly to zero at the band’s outer edge, so a contact entering or leaving the detected set does so at zero force and the gradient is continuous across the crossing. This is precisely what the margin was added for. The exception is a manifold truncated atMAX_MANIFOLD_POINTS: a point excluded by the cap can still carry impedance, and the gradient is one-sided there. - Stick/slide and load/unload transitions: the true derivative does not
exist at the exact transition; the IFT holds the regime the solver
converged to, so the reported gradient is the one-sided derivative of
that branch. This matches
phyz_contact::gradient’s documented contract. - Impact timing: at a genuine contact-making event the gradient of the
regularized model is biased relative to ideal rigid contact (Suh et al.,
ICML 2022); see
phyz_contact::gradient’s module docs.
§Failure policy
A step whose contact solve did not converge is not a fixed point and has
no IFT sensitivity; FixedPointSensitivity::at refuses it by design.
This adjoint degrades loudly: it returns
ConvexAdjointError::Unconverged (or
ConvexAdjointError::SingularKkt for a degenerate active set) rather
than substituting anything. A silent wrong gradient is the failure mode
this module exists to eliminate.
§Scope
Ground and body-body contacts. A body-body contact is frozen as a
feature pair (Anchor::Pair): both surface points in their own body’s
frame, the normal in the frame of the body owning the reference face, and
the contact point riding the other body’s vertex — so a lane sees the
contact rotate and translate with both bodies. That is what carries the
tangential-friction-between-two-moving-bodies channel — a foot on a board’s
grip tape, a hand on a lever — which is the case a ground-only adjoint
cannot express at all.
The per-vertex ∂J/∂(mesh vertex) channel of crate::rollout is not
reproduced here; that adjoint remains the (clearly documented) penalty-model
path for the vcad surface-gradient seam.
Re-exports§
pub use crate::rollout::N_INERTIA_PARAMS;
Structs§
- Convex
Adjoint Gradients - Everything one unified backward pass produces.
- Convex
Contact Rollout - A differentiable rollout through the forward simulator’s contact path.
Enums§
- Convex
Adjoint Error - Why an adjoint pass refused to produce a gradient.
Functions§
- convex_
adjoint_ gradient - One nominal rollout forward, one adjoint pass backward:
J,dJ/dq0,dJ/dv0,dJ/du_t, anddJ/dπper body — all through the convex contact model the forward simulator integrates. - convex_
rollout_ objective - Run the nominal rollout and return the objective only — the FD oracle for
gates, and a cheap primal probe. The dynamics are identical to
convex_adjoint_gradient’s forward pass (and toSimulator::step_with_contacts).