@@ -3185,70 +3185,121 @@ def func_hessian_and_cholesky_factor_incremental_sparse_batch(
31853185
31863186
31873187@qd .func
3188- def func_cholesky_factor_incremental_per_island_batch (
3188+ def func_rank1_update_island_constraint (
31893189 i_b ,
3190+ i_island ,
3191+ i_c ,
31903192 island_state : array_class .IslandState ,
31913193 constraint_state : array_class .ConstraintState ,
31923194 rigid_global_info : array_class .RigidGlobalInfo ,
31933195) -> bool :
3194- """Per- island analogue of func_hessian_and_cholesky_factor_incremental_sparse_batch .
3196+ """Apply one constraint's rank-1 update/downdate to its island's Cholesky block of L, in place in nt_H .
31953197
3196- Each changed constraint lies in a single island; its rank-1 update/downdate touches only that island's block of L
3197- (stored in nt_H at the island 's global DOF rows/cols, factored in place). The update runs over the island's local
3198- DOF positions ascending (dof_id is ascending, so local order is global-DOF order = the factor's processing order),
3199- bounded to the per-island skyline envelope (dof_env_start_local ). nt_vec is the rank-1 working vector, indexed by
3200- global DOF; it self-clears as columns are consumed and is zeroed up front so a degenerate break leaves it clean .
3198+ The update runs over the island's local DOF positions ascending (dof_id is ascending, so local order is global-DOF
3199+ order = the factor 's processing order), bounded to the per-island skyline envelope (dof_env_start_local). nt_vec is
3200+ the rank-1 working vector, indexed by global DOF; it self-clears as columns are consumed ( the caller zeroes the
3201+ island's entries once up front so a degenerate break leaves it clean ). Returns whether the downdate went indefinite,
3202+ in which case the caller refactors the island directly .
32013203 """
32023204 EPS = rigid_global_info .EPS [None ]
3203- n_dofs = constraint_state .nt_H .shape [1 ]
3204- n_islands = island_state .n_islands [i_b ]
3205+ dof_base = island_state .dof_slices .start [i_island , i_b ]
3206+ n = island_state .dof_slices .n [i_island , i_b ]
3207+ sign = 1.0 if constraint_state .active [i_c , i_b ] else - 1.0
3208+ efc_D_sqrt = qd .sqrt (constraint_state .efc_D [i_c , i_b ])
32053209
3206- for d in range (n_dofs ):
3207- constraint_state .nt_vec [d , i_b ] = gs .qd_float (0.0 )
3210+ for k_ in range (constraint_state .jac_n_dofs [i_c , i_b ]):
3211+ gd = constraint_state .jac_dofs_idx [i_c , k_ , i_b ]
3212+ constraint_state .nt_vec [gd , i_b ] = constraint_state .jac [i_c , gd , i_b ] * efc_D_sqrt
32083213
32093214 is_degenerated = False
3210- for idx in range (constraint_state .incr_n_changed [i_b ]):
3211- i_c = constraint_state .incr_changed_idx [idx , i_b ]
3212- i_island = 0
3213- if n_islands > 1 :
3214- i_island = island_state .constraint_island_idx [i_c , i_b ]
3215- dof_base = island_state .dof_slices .start [i_island , i_b ]
3216- n = island_state .dof_slices .n [i_island , i_b ]
3217- sign = 1.0 if constraint_state .active [i_c , i_b ] else - 1.0
3218- efc_D_sqrt = qd .sqrt (constraint_state .efc_D [i_c , i_b ])
3215+ for ld in range (n ):
3216+ gk = island_state .dof_id [dof_base + ld , i_b ]
3217+ vk = constraint_state .nt_vec [gk , i_b ]
3218+ if qd .abs (vk ) > EPS :
3219+ Lkk = constraint_state .nt_H [i_b , gk , gk ]
3220+ tmp = Lkk * Lkk + sign * vk * vk
3221+ if tmp < EPS :
3222+ is_degenerated = True
3223+ break
3224+ r = qd .sqrt (tmp )
3225+ cinv = Lkk / r
3226+ c = r / Lkk
3227+ s = vk / Lkk
3228+ constraint_state .nt_H [i_b , gk , gk ] = r
3229+ for jd in range (ld + 1 , n ):
3230+ if island_state .dof_env_start_local [dof_base + jd , i_b ] <= ld :
3231+ gj = island_state .dof_id [dof_base + jd , i_b ]
3232+ constraint_state .nt_H [i_b , gj , gk ] = (
3233+ constraint_state .nt_H [i_b , gj , gk ] + sign * s * constraint_state .nt_vec [gj , i_b ]
3234+ ) * cinv
3235+ constraint_state .nt_vec [gj , i_b ] = (
3236+ c * constraint_state .nt_vec [gj , i_b ] - s * constraint_state .nt_H [i_b , gj , gk ]
3237+ )
3238+ constraint_state .nt_vec [gk , i_b ] = gs .qd_float (0.0 )
3239+ return is_degenerated
32193240
3220- for k_ in range (constraint_state .jac_n_dofs [i_c , i_b ]):
3221- gd = constraint_state .jac_dofs_idx [i_c , k_ , i_b ]
3222- constraint_state .nt_vec [gd , i_b ] = constraint_state .jac [i_c , gd , i_b ] * efc_D_sqrt
32233241
3224- for ld in range (n ):
3225- gk = island_state .dof_id [dof_base + ld , i_b ]
3226- vk = constraint_state .nt_vec [gk , i_b ]
3227- if qd .abs (vk ) > EPS :
3228- Lkk = constraint_state .nt_H [i_b , gk , gk ]
3229- tmp = Lkk * Lkk + sign * vk * vk
3230- if tmp < EPS :
3231- is_degenerated = True
3232- break
3233- r = qd .sqrt (tmp )
3234- cinv = Lkk / r
3235- c = r / Lkk
3236- s = vk / Lkk
3237- constraint_state .nt_H [i_b , gk , gk ] = r
3238- for jd in range (ld + 1 , n ):
3239- if island_state .dof_env_start_local [dof_base + jd , i_b ] <= ld :
3240- gj = island_state .dof_id [dof_base + jd , i_b ]
3241- constraint_state .nt_H [i_b , gj , gk ] = (
3242- constraint_state .nt_H [i_b , gj , gk ] + sign * s * constraint_state .nt_vec [gj , i_b ]
3243- ) * cinv
3244- constraint_state .nt_vec [gj , i_b ] = (
3245- c * constraint_state .nt_vec [gj , i_b ] - s * constraint_state .nt_H [i_b , gj , gk ]
3246- )
3247- constraint_state .nt_vec [gk , i_b ] = gs .qd_float (0.0 )
3248- if is_degenerated :
3249- break
3242+ @qd .func
3243+ def func_factor_island_incremental_or_direct (
3244+ i_b ,
3245+ i_island ,
3246+ island_state : array_class .IslandState ,
3247+ entities_info : array_class .EntitiesInfo ,
3248+ constraint_state : array_class .ConstraintState ,
3249+ rigid_global_info : array_class .RigidGlobalInfo ,
3250+ static_rigid_sim_config : qd .template (),
3251+ ):
3252+ """Maintain one island's Cholesky factor for the current active set, choosing per island between an incremental
3253+ rank-1 update/downdate and a direct refactor.
3254+
3255+ One rank-1 update sweeps the island's skyline envelope at O(sum_span) (sum_span = total row span = envelope
3256+ nonzeros), so n_changed of them cost O(n_changed * sum_span); a direct refactor factors it at O(sum_span_sq)
3257+ (sum_span_sq = sum of squared row spans). Both costs are read straight off the envelope, so the decision compares
3258+ them directly - incremental while n_changed * sum_span < sum_span_sq - with no scene-tuned constant. The choice must
3259+ be per island, not on the env-wide flip count: the rebuild path refactors every island, so a global decision would
3260+ needlessly refactor quiescent islands whenever flips are spread thin across many of them (e.g. several separated
3261+ piles each toggling a single contact).
3262+ """
3263+ c_start = island_state .constraint_slices .start [i_island , i_b ]
3264+ c_n = island_state .constraint_slices .n [i_island , i_b ]
32503265
3251- return is_degenerated
3266+ n_changed = 0
3267+ for k in range (c_n ):
3268+ i_c = island_state .constraint_id [c_start + k , i_b ]
3269+ if constraint_state .active [i_c , i_b ] ^ constraint_state .prev_active [i_c , i_b ]:
3270+ n_changed = n_changed + 1
3271+
3272+ if n_changed > 0 :
3273+ dof_base = island_state .dof_slices .start [i_island , i_b ]
3274+ n_isl_dofs = island_state .dof_slices .n [i_island , i_b ]
3275+ # Estimate both costs from the skyline envelope: one rank-1 update sweeps the envelope at O(sum_span), a direct
3276+ # refactor factors it at O(sum_span_sq). Incremental wins while n_changed * sum_span < sum_span_sq, i.e. while
3277+ # n_changed stays below the flop-weighted effective bandwidth sum_span_sq / sum_span. No scene-tuned constant.
3278+ sum_span = gs .qd_float (0.0 )
3279+ sum_span_sq = gs .qd_float (0.0 )
3280+ for ld in range (n_isl_dofs ):
3281+ row_span = gs .qd_float (ld - island_state .dof_env_start_local [dof_base + ld , i_b ])
3282+ sum_span = sum_span + row_span
3283+ sum_span_sq = sum_span_sq + row_span * row_span
3284+ need_rebuild = gs .qd_float (n_changed ) * sum_span > sum_span_sq
3285+ if not need_rebuild :
3286+ for ld in range (n_isl_dofs ):
3287+ constraint_state .nt_vec [island_state .dof_id [dof_base + ld , i_b ], i_b ] = gs .qd_float (0.0 )
3288+ for k in range (c_n ):
3289+ i_c = island_state .constraint_id [c_start + k , i_b ]
3290+ if constraint_state .active [i_c , i_b ] ^ constraint_state .prev_active [i_c , i_b ]:
3291+ if func_rank1_update_island_constraint (
3292+ i_b , i_island , i_c , island_state , constraint_state , rigid_global_info
3293+ ):
3294+ need_rebuild = True
3295+ break
3296+ if need_rebuild :
3297+ func_hessian_direct_batch (
3298+ i_b , i_island , island_state , entities_info , constraint_state , rigid_global_info , static_rigid_sim_config
3299+ )
3300+ func_cholesky_factor_direct_batch (
3301+ i_b , i_island , island_state , constraint_state , rigid_global_info , static_rigid_sim_config
3302+ )
32523303
32533304
32543305@qd .func
@@ -5148,28 +5199,50 @@ def func_solve_iter(
51485199 if qd .static (static_rigid_sim_config .solver_type == gs .constraint_solver .Newton ):
51495200 # Within a step jac, M and efc_D are fixed, so H = M + J.T diag(D active) J depends only on the active mask;
51505201 # the linesearch only moves qacc, never H. func_solve_init already seeded the factor (nt_H holds L for the
5151- # seed's active set, and update_constraint above set prev_active to it), so every iteration including the
5152- # first is maintained incrementally: if no constraint flipped active the factor is reused as-is; if a few
5153- # flipped, the skyline factor is updated by a rank-1 update/downdate per changed constraint (whole-env or
5154- # per-island; much cheaper than reassembling and re-factoring). A degenerate downdate or a large active-set
5155- # change (> half the constraints) falls back to a direct rebuild, which `need_rebuild` selects through a
5156- # single call site (it is a large function, and each call site is compiled separately). The dense path uses
5157- # its own incremental rank-1 update.
5158- if qd .static (static_rigid_sim_config .sparse_solve ):
5202+ # seed's active set, and update_constraint above set prev_active to it), so every iteration is maintained
5203+ # rather than rebuilt: if no constraint flipped active the factor is reused as-is; if a few flipped, the
5204+ # skyline factor is updated by a rank-1 update/downdate per changed constraint; a degenerate downdate or a
5205+ # large active-set change falls back to a direct refactor. The per-island path decides this per island (the
5206+ # refactor is per island, so a global decision would needlessly rebuild quiescent islands); the whole-env
5207+ # sparse path and the dense path decide on the env-wide flip count.
5208+ if qd .static (
5209+ static_rigid_sim_config .sparse_solve
5210+ and static_rigid_sim_config .enable_per_island_solve
5211+ and not static_rigid_sim_config .sparse_envelope
5212+ ):
5213+ for i_island in range (island_state .n_islands [i_b ]):
5214+ if qd .static (static_rigid_sim_config .use_hibernation ):
5215+ if island_state .is_hibernated [i_island , i_b ]:
5216+ continue
5217+ func_factor_island_incremental_or_direct (
5218+ i_b ,
5219+ i_island ,
5220+ island_state ,
5221+ entities_info ,
5222+ constraint_state ,
5223+ rigid_global_info ,
5224+ static_rigid_sim_config ,
5225+ )
5226+ elif qd .static (static_rigid_sim_config .sparse_solve ):
51595227 func_build_changed_constraint_list (i_b , constraint_state = constraint_state )
51605228 n_changed = constraint_state .incr_n_changed [i_b ]
51615229 need_rebuild = True
51625230 if n_changed == 0 :
51635231 need_rebuild = False
5164- elif n_changed * 2 <= constraint_state .n_constraints [i_b ]:
5165- if qd .static (static_rigid_sim_config .sparse_envelope ):
5232+ elif qd .static (static_rigid_sim_config .sparse_envelope ):
5233+ # Same crossover as the per-island path, on the whole-env skyline (nt_H_env_start): incremental
5234+ # beats a refactor while n_changed * sum_span < sum_span_sq (the flop-weighted effective bandwidth).
5235+ n_dofs = constraint_state .nt_H .shape [1 ]
5236+ sum_span = gs .qd_float (0.0 )
5237+ sum_span_sq = gs .qd_float (0.0 )
5238+ for p in range (n_dofs ):
5239+ row_span = gs .qd_float (p - constraint_state .nt_H_env_start [i_b , p ])
5240+ sum_span = sum_span + row_span
5241+ sum_span_sq = sum_span_sq + row_span * row_span
5242+ if gs .qd_float (n_changed ) * sum_span <= sum_span_sq :
51665243 need_rebuild = func_hessian_and_cholesky_factor_incremental_sparse_batch (
51675244 i_b , constraint_state , rigid_global_info
51685245 )
5169- elif qd .static (static_rigid_sim_config .enable_per_island_solve ):
5170- need_rebuild = func_cholesky_factor_incremental_per_island_batch (
5171- i_b , island_state , constraint_state , rigid_global_info
5172- )
51735246 if need_rebuild :
51745247 func_hessian_and_cholesky_factor_direct_batch (
51755248 i_b ,
0 commit comments