@@ -94,6 +94,60 @@ void transpose_spd_fill_values(const stacked_pd *A, stacked_pd *C)
9494 coalesce_spd_fill_values (raw , C );
9595}
9696
97+ // ====================================================================================
98+ // Shared "spd blockwise + coalesce-accumulate" skeleton.
99+ //
100+ // Used by kernels of the shape C = f(B, X) where B is a stacked_pd and each
101+ // per-block partial f(B_k, X) is a PD. The partials' row_perms (and cells)
102+ // may overlap across k, so we collect them via new_stacked_pd_unchecked,
103+ // coalesce with the _unchecked variant, and use the accumulating scatter
104+ // at fill time. The raw spd is stashed on out->pre_coalesce so fill can
105+ // refresh per-block values without reallocating structure.
106+ //
107+ // Three kernel pairs build on this skeleton: BTA_spd_pd / BTDA_spd_pd,
108+ // BTA_spd_csc / BTDA_spd_csc, and ATA_spd / ATDA_spd. Each variant supplies
109+ // per-block alloc and fill kernels via callback + context.
110+ // ====================================================================================
111+ typedef matrix * (* spd_per_block_alloc_fn )(const permuted_dense * Bk , const void * ctx );
112+
113+ typedef void (* spd_per_block_fill_fn )(const permuted_dense * Bk , const double * d ,
114+ const void * ctx , permuted_dense * Ck );
115+
116+ static matrix * spd_blockwise_alloc_coalesce (const stacked_pd * B , int Cn ,
117+ spd_per_block_alloc_fn op ,
118+ const void * ctx )
119+ {
120+ int n_blocks = B -> n_blocks ;
121+ permuted_dense * * partials =
122+ (permuted_dense * * ) SP_MALLOC (n_blocks * sizeof (permuted_dense * ));
123+ for (int k = 0 ; k < n_blocks ; k ++ )
124+ {
125+ partials [k ] = (permuted_dense * ) op (B -> blocks [k ], ctx );
126+ }
127+ matrix * raw =
128+ new_stacked_pd_unchecked (B -> base .n , Cn , n_blocks , partials , NULL , NULL );
129+ free (partials );
130+ matrix * C = coalesce_spd_alloc_unchecked ((stacked_pd * ) raw );
131+ ((stacked_pd * ) C )-> pre_coalesce = (stacked_pd * ) raw ;
132+ return C ;
133+ }
134+
135+ static void spd_blockwise_fill_coalesce_accumulate (const stacked_pd * B ,
136+ const double * d , const void * ctx ,
137+ stacked_pd * C ,
138+ spd_per_block_fill_fn op )
139+ {
140+ if (C -> base .nnz == 0 ) return ;
141+
142+ stacked_pd * raw = C -> pre_coalesce ;
143+ for (int k = 0 ; k < B -> n_blocks ; k ++ )
144+ {
145+ op (B -> blocks [k ], d , ctx , raw -> blocks [k ]);
146+ }
147+ memset (C -> base .x , 0 , C -> base .nnz * sizeof (double ));
148+ coalesce_spd_fill_values_accumulate (raw , C );
149+ }
150+
97151// ------------------------------------------------------------------------------------
98152// C = ATDA for stacked_pd A. Let A = [A1; A2; A3] where Ai has n columns (the same
99153// number as A). Then ATDA = A1^T D1 A1 + A2^T D2 A2 + A3^T D3 A3. Term i and j
@@ -108,50 +162,33 @@ share cells.The output groups cols of A by signature sig_C(c) = {k: c ∈ C_k};
108162 and col_perm = ⋃ C_k for k in the signature. No structural zeros.
109163 The output's `pre_coalesce` slot holds per-source scratch PDs (one symmetric
110164 PD per source block, row_perm = col_perm = C_k) that
111- ATDA_spd_fill_values writes into. */
112- matrix * ATA_spd_alloc (const stacked_pd * A )
165+ ATDA_spd_fill_values writes into.
166+
167+ Note on per-block kernel choice: we use ATA_pd_alloc (rather than
168+ new_permuted_dense directly) because it also pre-sizes
169+ A->blocks[k]->kernel_dwork — that scratch buffer is what
170+ ATDA_pd_fill_values reads from during the fill phase. */
171+ static matrix * wrapper_ATA_pd (const permuted_dense * Ak , const void * ctx )
113172{
114- int n = A -> base .n ;
115- int n_blocks = A -> n_blocks ;
173+ (void ) ctx ;
174+ return ATA_pd_alloc (Ak );
175+ }
116176
117- /* Pseudo-spd: per source block k, a square PD with
118- row_perm = col_perm = C_k and uninitialized X. We use the
119- PD-level ATA_pd_alloc here (rather than new_permuted_dense
120- directly) because it also pre-sizes A->blocks[k]->kernel_dwork — that
121- scratch buffer is what ATDA_pd_fill_values reads from during the
122- fill phase. */
123- permuted_dense * * scratch_blocks =
124- (permuted_dense * * ) SP_MALLOC (n_blocks * sizeof (permuted_dense * ));
125- for (int k = 0 ; k < n_blocks ; k ++ )
126- {
127- scratch_blocks [k ] = (permuted_dense * ) ATA_pd_alloc (A -> blocks [k ]);
128- }
129- matrix * scratch =
130- new_stacked_pd_unchecked (n , n , n_blocks , scratch_blocks , NULL , NULL );
131- free (scratch_blocks );
177+ static void wrapper_ATDA_pd (const permuted_dense * Ak , const double * d ,
178+ const void * ctx , permuted_dense * Ck )
179+ {
180+ (void ) ctx ;
181+ ATDA_pd_fill_values (Ak , d , Ck );
182+ }
132183
133- /* prepare coalescing of overlapping scratch blocks */
134- matrix * C = coalesce_spd_alloc_unchecked ((stacked_pd * ) scratch );
135- ((stacked_pd * ) C )-> pre_coalesce = (stacked_pd * ) scratch ;
136- return C ;
184+ matrix * ATA_spd_alloc (const stacked_pd * A )
185+ {
186+ return spd_blockwise_alloc_coalesce (A , A -> base .n , wrapper_ATA_pd , NULL );
137187}
138188
139189void ATDA_spd_fill_values (const stacked_pd * A , const double * d , stacked_pd * C )
140190{
141- stacked_pd * scratch = C -> pre_coalesce ;
142-
143- /* compute Ai^T @ Di @ Ai into the scratch PDs. */
144- for (int k = 0 ; k < A -> n_blocks ; k ++ )
145- {
146- ATDA_pd_fill_values (A -> blocks [k ], d , scratch -> blocks [k ]);
147- }
148-
149- /* zero C (one memset is sufficient since, by design, the values of
150- different blocks are stored consecutively in memory). */
151- memset (C -> base .x , 0 , C -> base .nnz * sizeof (double ));
152-
153- /* scatter + add each Ai^T Di Ai into its destination C block. */
154- coalesce_spd_fill_values_accumulate (scratch , C );
191+ spd_blockwise_fill_coalesce_accumulate (A , d , NULL , C , wrapper_ATDA_pd );
155192}
156193
157194// ---------------------------------------------------------------------------------
@@ -344,52 +381,62 @@ void BTDA_pd_spd_fill_values(const permuted_dense *B, const double *d,
344381//
345382// B = B1 + B2 + B3 where each Bi is a global permuted dense. Then
346383// C = B^T D A = C1 + C2 + C3 where Ci = Bi^T D A.
384+ //
385+ // TODO: each BTDA_pd_pd_fill_values call internally allocates a DA intermediate
386+ // (see permuted_dense_linalg.c BTDA_pd_pd_fill_values). That means the BTDA
387+ // variant here allocates n_blocks DA temps per fill, all on the hot Hessian
388+ // path. Must be fixed — same future remedy as the per-block BTDA: fold
389+ // diag(d) directly into BTA_pd_pd's gather step.
347390// ---------------------------------------------------------------------------------
348- matrix * BTA_spd_pd_alloc (const stacked_pd * B , const permuted_dense * A )
391+ static matrix * wrapper_BTA_pd_pd (const permuted_dense * Bk , const void * ctx )
349392{
350- int n_blocks = B -> n_blocks ;
351-
352- /* Ck = B_k^T @ A */
353- permuted_dense * * Cks =
354- (permuted_dense * * ) SP_MALLOC (n_blocks * sizeof (permuted_dense * ));
355- for (int k = 0 ; k < n_blocks ; k ++ )
356- {
357- Cks [k ] = (permuted_dense * ) BTA_pd_pd_alloc (B -> blocks [k ], A );
358- }
393+ return BTA_pd_pd_alloc (Bk , (const permuted_dense * ) ctx );
394+ }
359395
360- matrix * raw =
361- new_stacked_pd_unchecked (B -> base .n , A -> base .n , n_blocks , Cks , NULL , NULL );
362- free (Cks );
396+ static void wrapper_BTDA_pd_pd (const permuted_dense * Bk , const double * d ,
397+ const void * ctx , permuted_dense * Ck )
398+ {
399+ BTDA_pd_pd_fill_values (Bk , d , (const permuted_dense * ) ctx , Ck );
400+ }
363401
364- /* coalesce */
365- matrix * C = coalesce_spd_alloc_unchecked ((stacked_pd * ) raw );
366- ((stacked_pd * ) C )-> pre_coalesce = (stacked_pd * ) raw ;
367- return C ;
402+ matrix * BTA_spd_pd_alloc (const stacked_pd * B , const permuted_dense * A )
403+ {
404+ return spd_blockwise_alloc_coalesce (B , A -> base .n , wrapper_BTA_pd_pd , A );
368405}
369406
370407void BTDA_spd_pd_fill_values (const stacked_pd * B , const double * d ,
371408 const permuted_dense * A , stacked_pd * C )
372409{
373- if (C -> base .nnz == 0 )
374- {
375- return ;
376- }
410+ spd_blockwise_fill_coalesce_accumulate (B , d , A , C , wrapper_BTDA_pd_pd );
411+ }
377412
378- /* TODO: each BTDA_pd_pd_fill_values call internally allocates a DA
379- intermediate (see permuted_dense_linalg.c BTDA_pd_pd_fill_values).
380- That means this function allocates n_blocks DA temps per fill,
381- all on the hot Hessian path. Must be fixed — same future remedy
382- as the per-block BTDA: fold diag(d) directly into BTA_pd_pd's
383- gather step. */
384- stacked_pd * raw = C -> pre_coalesce ;
385- for ( int k = 0 ; k < B -> n_blocks ; k ++ )
386- {
387- BTDA_pd_pd_fill_values ( B -> blocks [ k ], d , A , raw -> blocks [ k ] );
388- }
413+ // ---------------------------------------------------------------------------------
414+ // BTA_spd_csc / BTDA_spd_csc: C = B^T @ (diag(d) @) A where B is stacked_pd,
415+ // A is CSC. Output C is stacked_pd. Same two-stage skeleton as BTA_spd_pd /
416+ // BTDA_spd_pd, just with BTA_pd_csc / BTDA_pd_csc as the per-block kernel.
417+ // Unlike the pd-on-the-right path, BTDA_pd_csc reuses the per-block
418+ // B_k->kernel_dwork sized by BTA_pd_csc_alloc — no temp DA allocation.
419+ // ---------------------------------------------------------------------------------
420+ static matrix * wrapper_BTA_pd_csc ( const permuted_dense * Bk , const void * ctx )
421+ {
422+ return BTA_pd_csc_alloc ( Bk , ( const CSC_matrix * ) ctx );
423+ }
389424
390- /* zero C before accumulating (matches ATDA_spd_fill_values). */
391- memset (C -> base .x , 0 , C -> base .nnz * sizeof (double ));
392- coalesce_spd_fill_values_accumulate (raw , C );
425+ static void wrapper_BTDA_pd_csc (const permuted_dense * Bk , const double * d ,
426+ const void * ctx , permuted_dense * Ck )
427+ {
428+ BTDA_pd_csc_fill_values (Bk , d , (const CSC_matrix * ) ctx , Ck );
429+ }
430+
431+ matrix * BTA_spd_csc_alloc (const stacked_pd * B , const CSC_matrix * A )
432+ {
433+ return spd_blockwise_alloc_coalesce (B , A -> n , wrapper_BTA_pd_csc , A );
434+ }
435+
436+ void BTDA_spd_csc_fill_values (const stacked_pd * B , const double * d ,
437+ const CSC_matrix * A , stacked_pd * C )
438+ {
439+ spd_blockwise_fill_coalesce_accumulate (B , d , A , C , wrapper_BTDA_pd_csc );
393440}
394441
395442// ---------------------------------------------------------------------------------
0 commit comments