@@ -1096,6 +1096,20 @@ typedef struct {
10961096 int ncone ; // number of contacts in cone state
10971097 int nupdate ; // number of Cholesky updates
10981098
1099+ // cone fold gate (elliptic sparse)
1100+ int * pathcost ; // reverse-etree path costs of L rows (nv x 1)
1101+ mjtSize Lflops ; // sum of squared L row nonzeros (2x factorization MACs)
1102+ int cone_fold ; // cached fold decision (-1: recompute)
1103+
1104+ // cone fold workspace, allocated on the first fold (elliptic sparse)
1105+ mjtNum * Jmod ; // Jacobian, cone rows replaced by Lc'*Jc (nJ x 1)
1106+ mjtNum * JTmod ; // transpose of Jmod (nJ x 1)
1107+ mjtNum * Dmod ; // constraint inertia, cone rows set to 1 (nefc x 1)
1108+ mjtNum * Hcone ; // cone-inclusive Hessian (nH x 1)
1109+ int * JTmod_rownnz ; // Jmod transpose row nonzeros (nv x 1)
1110+ int * JTmod_rowadr ; // Jmod transpose row addresses (nv x 1)
1111+ int * JTmod_colind ; // Jmod transpose column indices (nJ x 1)
1112+
10991113 // linesearch diagnostics
11001114 int LSiter ; // number of linesearch iterations
11011115 int LSresult ; // linesearch result
@@ -1113,6 +1127,7 @@ static void PrimalPointers(const mjModel* m, const mjData* d, mjPrimalContext* c
11131127 ctx -> is_elliptic = (m -> opt .cone == mjCONE_ELLIPTIC );
11141128 ctx -> contact = d -> contact ;
11151129 ctx -> island = island ;
1130+ ctx -> cone_fold = -1 ;
11161131
11171132 // set sizes and pointers (monolithic)
11181133 if (island < 0 ) {
@@ -2365,13 +2380,14 @@ static void MakeHessian(mjData* d, mjPrimalContext* ctx) {
23652380 nv , d );
23662381
23672382 // nL is known: allocate blocks and carve L_colind, LT_colind, LT_map, L, Lcone
2368- size_t nL_int = 2 * ctx -> nL + ctx -> nL ; // L_colind + LT_colind + LT_map
2369- size_t nL_num = ctx -> is_elliptic ? 2 * ctx -> nL : ctx -> nL ; // L + Lcone
2383+ size_t nL_int = 3 * ctx -> nL + ( ctx -> is_elliptic ? nv : 0 ); // L_colind + LT_colind + LT_map + pathcost
2384+ size_t nL_num = ctx -> is_elliptic ? 2 * ctx -> nL : ctx -> nL ; // L + Lcone
23702385 int * L_intblock = mjSTACKALLOC (d , nL_int , int );
23712386 mjtNum * L_numblock = mjSTACKALLOC (d , nL_num , mjtNum );
23722387 ctx -> L_colind = L_intblock ;
23732388 ctx -> LT_colind = L_intblock + ctx -> nL ;
23742389 ctx -> LT_map = L_intblock + 2 * ctx -> nL ;
2390+ ctx -> pathcost = ctx -> is_elliptic ? L_intblock + 3 * ctx -> nL : NULL ;
23752391 ctx -> L = L_numblock ;
23762392 ctx -> Lcone = ctx -> is_elliptic ? L_numblock + ctx -> nL : NULL ;
23772393
@@ -2380,6 +2396,20 @@ static void MakeHessian(mjData* d, mjPrimalContext* ctx) {
23802396 ctx -> LT_colind , ctx -> LT_rownnz , ctx -> LT_rowadr , ctx -> LT_map ,
23812397 ctx -> HT_rownnz , ctx -> HT_rowadr , ctx -> HT_colind ,
23822398 nv , d );
2399+
2400+ // cone fold gate inputs, fixed for the solve since the L pattern is fixed:
2401+ // pathcost[r] totals the L row nonzeros along the reverse-etree path from
2402+ // row r (parent(r) = largest off-diagonal column of L row r), the cost of
2403+ // a rank-1 update starting at column r; Lflops totals squared row nonzeros
2404+ if (ctx -> is_elliptic ) {
2405+ ctx -> Lflops = 0 ;
2406+ for (int r = 0 ; r < nv ; r ++ ) {
2407+ int nnz = ctx -> L_rownnz [r ];
2408+ int parent = nnz >= 2 ? ctx -> L_colind [ctx -> L_rowadr [r ] + nnz - 2 ] : -1 ;
2409+ ctx -> pathcost [r ] = nnz + (parent >= 0 ? ctx -> pathcost [parent ] : 0 );
2410+ ctx -> Lflops += (mjtSize )nnz * nnz ;
2411+ }
2412+ }
23832413 }
23842414
23852415 // dense
@@ -2476,6 +2506,7 @@ static void FactorizeHessian(mjData* d, mjPrimalContext* ctx, int flg_recompute)
24762506
24772507 // add cones to factor if present
24782508 if (ctx -> ncone ) {
2509+ ctx -> cone_fold = -1 ;
24792510 HessianCone (d , ctx );
24802511 }
24812512
@@ -2484,8 +2515,80 @@ static void FactorizeHessian(mjData* d, mjPrimalContext* ctx, int flg_recompute)
24842515}
24852516
24862517
2487- // elliptic case: Hcone = H + cone_contributions
2488- static void HessianCone (mjData * d , mjPrimalContext * ctx ) {
2518+ // elliptic sparse case with many cones: rebuild Lcone with one factorization.
2519+ static void HessianConeFolded (mjData * d , mjPrimalContext * ctx ) {
2520+ int nv = ctx -> nv , nefc = ctx -> nefc ;
2521+ mjtNum local [36 ];
2522+
2523+ // allocate on the first fold of this solve and reuse for the rest of it
2524+ if (!ctx -> Jmod ) {
2525+ ctx -> Jmod = mjSTACKALLOC (d , ctx -> nJ , mjtNum );
2526+ ctx -> JTmod = mjSTACKALLOC (d , ctx -> nJ , mjtNum );
2527+ ctx -> Dmod = mjSTACKALLOC (d , nefc , mjtNum );
2528+ ctx -> Hcone = mjSTACKALLOC (d , ctx -> nH , mjtNum );
2529+ ctx -> JTmod_rownnz = mjSTACKALLOC (d , nv , int );
2530+ ctx -> JTmod_rowadr = mjSTACKALLOC (d , nv , int );
2531+ ctx -> JTmod_colind = mjSTACKALLOC (d , ctx -> nJ , int );
2532+ }
2533+ mjtNum * Jmod = ctx -> Jmod ;
2534+ mjtNum * JTmod = ctx -> JTmod ;
2535+ mjtNum * Dmod = ctx -> Dmod ;
2536+ mjtNum * Hcone = ctx -> Hcone ;
2537+ int * JT_rownnz = ctx -> JTmod_rownnz ;
2538+ int * JT_rowadr = ctx -> JTmod_rowadr ;
2539+ int * JT_colind = ctx -> JTmod_colind ;
2540+
2541+ mju_copy (Jmod , ctx -> J , ctx -> nJ );
2542+ for (int i = 0 ; i < nefc ; i ++ ) {
2543+ Dmod [i ] = ctx -> efc_state [i ] == mjCNSTRSTATE_QUADRATIC ? ctx -> efc_D [i ] : 0 ;
2544+ }
2545+
2546+ // replace cone contact rows with Lc'*Jc, where Hc = Lc*Lc'
2547+ for (int i = 0 ; i < nefc ; i ++ ) {
2548+ if (ctx -> efc_state [i ] == mjCNSTRSTATE_CONE ) {
2549+ int dim = ctx -> contact [ctx -> efc_id [i ]].dim ;
2550+ mju_copy (local , ctx -> contact [ctx -> efc_id [i ]].H , dim * dim );
2551+ mju_cholFactor (local , dim , mjMINVAL );
2552+
2553+ const int nnz = ctx -> J_rownnz [i ];
2554+ for (int c = 0 ; c < dim ; c ++ ) {
2555+ mjtNum * dst = Jmod + ctx -> J_rowadr [i + c ];
2556+ mju_zero (dst , nnz );
2557+ for (int r = c ; r < dim ; r ++ ) {
2558+ mju_addToScl (dst , ctx -> J + ctx -> J_rowadr [i + r ], local [r * dim + c ], nnz );
2559+ }
2560+ Dmod [i + c ] = 1 ;
2561+ }
2562+ ctx -> nupdate += dim ;
2563+ i += (dim - 1 );
2564+ }
2565+ }
2566+
2567+ // Lcone = chol(Jmod'*Dmod*Jmod + M), same sparsity as L
2568+ mju_transposeSparse (JTmod , Jmod , nefc , nv , JT_rownnz , JT_rowadr , JT_colind , NULL ,
2569+ ctx -> J_rownnz , ctx -> J_rowadr , ctx -> J_colind );
2570+ mju_sqrMatTDSparseNumeric (Hcone , nv , ctx -> H_rownnz , ctx -> H_rowadr , ctx -> H_colind , NULL ,
2571+ Jmod , ctx -> J_rownnz , ctx -> J_rowadr , ctx -> J_colind ,
2572+ JTmod , JT_rownnz , JT_rowadr , JT_colind ,
2573+ ctx -> JT_rowsuper , Dmod , d );
2574+ mju_addToMatSparse (Hcone , ctx -> H_rownnz , ctx -> H_rowadr , ctx -> H_colind , nv ,
2575+ ctx -> M , ctx -> M_rownnz , ctx -> M_rowadr , ctx -> M_colind );
2576+ if (ctx -> nS ) {
2577+ mju_addToMatSparse (Hcone , ctx -> H_rownnz , ctx -> H_rowadr , ctx -> H_colind , nv ,
2578+ ctx -> S_val , ctx -> S_rownnz , ctx -> S_rowadr , ctx -> S_colind );
2579+ }
2580+ if (mju_cholFactorNumeric (ctx -> Lcone , nv , mjMINVAL ,
2581+ ctx -> L_rownnz , ctx -> L_rowadr , ctx -> L_colind ,
2582+ ctx -> LT_rownnz , ctx -> LT_rowadr , ctx -> LT_colind , ctx -> LT_map ,
2583+ Hcone , ctx -> H_rownnz , ctx -> H_rowadr , ctx -> H_colind ,
2584+ ctx -> cholscratch ) != nv ) {
2585+ mjERROR ("rank-deficient cone Hessian" );
2586+ }
2587+ }
2588+
2589+
2590+ // elliptic case, update path: Lcone = L, then dim rank-1 updates per cone contact
2591+ static void HessianConeUpdate (mjData * d , mjPrimalContext * ctx ) {
24892592 int nv = ctx -> nv , nefc = ctx -> nefc ;
24902593 mjtNum * LTJ = ctx -> LTJ ;
24912594 mjtNum local [36 ];
@@ -2550,6 +2653,52 @@ static void HessianCone(mjData* d, mjPrimalContext* ctx) {
25502653}
25512654
25522655
2656+ // predict whether folding is faster than rank-1 updates for the current set, comparing flop counts:
2657+ // update path: U = sum over cone contacts of dim * pathcost of the contact's last dof (a rank-1
2658+ // update walks the reverse-etree path of its last dof; lower dofs merge into it)
2659+ // folded path: Lflops/2 for the factorization plus S for J'*D*J over the D != 0 rows
2660+ // fold when 10 U > 3 (Lflops + 2 S): an update flop costs about twice a factorization flop
2661+ static int HessianConeFoldFaster (const mjPrimalContext * ctx ) {
2662+ int nefc = ctx -> nefc ;
2663+
2664+ // U: update flops, S: J'*D*J flops
2665+ mjtSize U = 0 , S = 0 ;
2666+ for (int i = 0 ; i < nefc ; i ++ ) {
2667+ int nnz = ctx -> J_rownnz [i ];
2668+ if (ctx -> efc_state [i ] == mjCNSTRSTATE_QUADRATIC ) {
2669+ S += (mjtSize )nnz * nnz ;
2670+ } else if (ctx -> efc_state [i ] == mjCNSTRSTATE_CONE ) {
2671+ int dim = ctx -> contact [ctx -> efc_id [i ]].dim ;
2672+ int lastcol = ctx -> J_colind [ctx -> J_rowadr [i ] + nnz - 1 ];
2673+ U += (mjtSize )dim * ctx -> pathcost [lastcol ];
2674+ S += (mjtSize )dim * nnz * nnz ;
2675+ i += dim - 1 ;
2676+ }
2677+ }
2678+
2679+ return 10 * U > 3 * (ctx -> Lflops + 2 * S );
2680+ }
2681+
2682+
2683+ // elliptic case: Hcone = H + cone_contributions
2684+ static void HessianCone (mjData * d , mjPrimalContext * ctx ) {
2685+ // sparse mode with many sliding contacts: one refactorization can beat
2686+ // sum(dim) rank-1 updates per cone contact. The decision depends only on
2687+ // sparsity patterns and efc_state, so it is cached until a state change.
2688+ if (ctx -> is_sparse ) {
2689+ if (ctx -> cone_fold < 0 ) {
2690+ ctx -> cone_fold = HessianConeFoldFaster (ctx );
2691+ }
2692+ if (ctx -> cone_fold ) {
2693+ HessianConeFolded (d , ctx );
2694+ return ;
2695+ }
2696+ }
2697+
2698+ HessianConeUpdate (d , ctx );
2699+ }
2700+
2701+
25532702// incremental update to Hessian factor due to changes in efc_state
25542703static void HessianIncremental (mjData * d , mjPrimalContext * ctx , const int * oldstate ) {
25552704 int rank , nv = ctx -> nv , nefc = ctx -> nefc ;
@@ -2562,6 +2711,11 @@ static void HessianIncremental(mjData* d, mjPrimalContext* ctx, const int* oldst
25622711 for (int i = 0 ; i < nefc ; i ++ ) {
25632712 int flag_update = -1 ;
25642713
2714+ // any state change invalidates the cached cone fold decision
2715+ if (oldstate [i ] != ctx -> efc_state [i ]) {
2716+ ctx -> cone_fold = -1 ;
2717+ }
2718+
25652719 // add quad
25662720 if (oldstate [i ] != mjCNSTRSTATE_QUADRATIC && ctx -> efc_state [i ] == mjCNSTRSTATE_QUADRATIC ) {
25672721 flag_update = 1 ;
0 commit comments