Replace the flex metric factorization with a block preconditioner
Every step, the flex block of the implicit effective metric M + K was factorized by sparse Cholesky, because K depends on the configuration. On model/flex/bag.xml, added here, that is roughly half the step, against a comparable share for the constraint solve it exists to accelerate. Keep only the metric's per-vertex 3x3 diagonal blocks, prefactored. Neither consumer needs the exact inverse: the CG constraint solver only wants a preconditioner, and qacc_smooth can come from an iterative solve using those blocks. They are O(n) to build and to apply, but weaker, so CG runs about twice the iterations and qacc_smooth becomes an iteration rather than a direct solve. Net, the bag model steps roughly twice as fast. The preconditioner, by metric state. Inactive, meaning no flex elasticity or an explicit integrator: M^-1, unchanged. Bending only (nefmK == 0): M^-1 plus the exact constant bending factor from mj_setConst on the dofs it covers, unchanged; that factor is built at model compile time and costs nothing per step. Per-step stiffness: M^-1 plus the 3x3 blocks, where before it was a per-step sparse Cholesky, or, when M couples across the flex block, an inner PCG of up to 50 iterations run once per outer CG iteration. Only models carrying per-step stretch stiffness change in wall-clock. Both ponchos hold their timing and take slightly fewer CG iterations than before, because the preconditioner is now symmetric: it applies M^-1 and the covered blocks to disjoint sets of dofs, where previously the two overlapped and the operator was not symmetric, which PCG requires. mjd_effSolve is the accurate solve of (M + K)x = b; what used to carry that name only preconditions and is now mjd_effPrec. Its CG guarded the division by pAp with mjMINVAL, an absolute floor on a quantity that scales with the square of the right-hand side, so a small b aborted the solve while the curvature was healthy: four flex models were quietly left short of tolerance. For an SPD metric the guard is positivity, and with that the same solves converge. The qacc_smooth call site in mj_fwdAcceleration is textually unchanged but now reaches the iterative solve, which converges on opt.tolerance rather than a hardcoded threshold, floored in mjUSESINGLE builds where the squared target is unreachable in float. Reaching the iteration cap names the ill-conditioned flex stiffness and then reports it through mjWARN_INERTIA, rather than returning an under-converged result. Covered dofs are located by walking the covered rows of the stiffness matrix, as they need not be 3-aligned from dof 0: any joint declared before a flexcomp shifts them. mjData.efm_L_rownnz, efm_L_rowadr and efm_L_colind described the sparsity of the deleted factorization and are removed: left NULL with nonzero mjxmacro extents they made the Python bindings hand back uninitialized arrays. efm_active loses the value 2 for the same reason, nothing selects a solve path on preconditioner exactness any more. Both are recorded under breaking changes. model/flex/bag.xml is added because no shipped model carried per-step stretch stiffness. The ponchos are bending-only and trampoline.xml uses an explicit integrator, so the metric never activates there. It is excluded from WriteReadCompareTest: stretch stiffness amplifies rest geometry that XML rounds on save.
This commit is contained in:
@@ -136,10 +136,10 @@ typedef struct mjData_ {
|
||||
int nl; // number of limit constraints
|
||||
int nefc; // number of constraints
|
||||
int nJ; // number of non-zeros in constraint Jacobian
|
||||
int efm_active; // implicit effective metric M+K: 0 inactive, 1 active, 2 active + preconditioner exact
|
||||
int efm_active; // implicit effective metric M+K is active (see mjd_effBuild)
|
||||
int nefmK; // number of non-zeros in effective-stiffness CSR
|
||||
int nefmdof; // number of rows in effective-metric factor
|
||||
int nefmL; // number of non-zeros in the effective-metric factor
|
||||
int nefmdof; // number of 3x3 blocks in the effective-metric preconditioner
|
||||
int nefmL; // size of the effective-metric block storage (9*nefmdof)
|
||||
int nY; // number of non-zeros in constraint inverse inertia square root
|
||||
int nA; // number of non-zeros in constraint inverse inertia matrix
|
||||
int nisland; // number of detected constraint islands
|
||||
@@ -406,11 +406,8 @@ typedef struct mjData_ {
|
||||
int* efm_K_rowadr; // effective-stiffness CSR row addresses (nv x 1)
|
||||
int* efm_K_colind; // effective-stiffness CSR column indices (nefmK x 1)
|
||||
mjtNum* efm_K_val; // effective-stiffness CSR values (nefmK x 1)
|
||||
int* efm_dofid; // factor row -> dof address (nefmdof x 1)
|
||||
int* efm_L_rownnz; // factor row nonzeros (nefmdof x 1)
|
||||
int* efm_L_rowadr; // factor row addresses (nefmdof x 1)
|
||||
int* efm_L_colind; // factor column indices (nefmL x 1)
|
||||
mjtNum* efm_L; // Cholesky factor of diag(M)+K, covered dofs (nefmL x 1)
|
||||
int* efm_dofid; // block k -> dof address of its vertex triple (nefmdof x 1)
|
||||
mjtNum* efm_L; // factored 3x3 diagonal blocks of M+K (nefmL x 1)
|
||||
|
||||
//-------------------- arena-allocated: POSITION, VELOCITY, CONTROL/ACCELERATION dependent
|
||||
|
||||
|
||||
@@ -1015,9 +1015,6 @@
|
||||
X ( int, efm_K_colind, MJ_D(nefmK), 1 ) \
|
||||
X ( mjtNum, efm_K_val, MJ_D(nefmK), 1 ) \
|
||||
X ( int, efm_dofid, MJ_D(nefmdof), 1 ) \
|
||||
X ( int, efm_L_rownnz, MJ_D(nefmdof), 1 ) \
|
||||
X ( int, efm_L_rowadr, MJ_D(nefmdof), 1 ) \
|
||||
X ( int, efm_L_colind, MJ_D(nefmL), 1 ) \
|
||||
X ( mjtNum, efm_L, MJ_D(nefmL), 1 )
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user