@@ -462,4 +462,232 @@ const char *test_coalesce_empty_input(void)
462462 return 0 ;
463463}
464464
465+ /* Transpose where source blocks have disjoint rows AND disjoint cols.
466+ Transposed blocks also have disjoint rows; coalesce leaves them
467+ unchanged. */
468+ const char * test_transpose_spd_no_overlap (void )
469+ {
470+ /* block 0: rows {0, 1}, cols {0, 1}, X = [[1, 2], [3, 4]]
471+ block 1: rows {2, 3}, cols {2, 3}, X = [[5, 6], [7, 8]] */
472+ int row_perm_0 [2 ] = {0 , 1 };
473+ int col_perm_0 [2 ] = {0 , 1 };
474+ double X0 [4 ] = {1.0 , 2.0 , 3.0 , 4.0 };
475+ matrix * blk0 = new_permuted_dense (4 , 4 , 2 , 2 , row_perm_0 , col_perm_0 , X0 );
476+
477+ int row_perm_1 [2 ] = {2 , 3 };
478+ int col_perm_1 [2 ] = {2 , 3 };
479+ double X1 [4 ] = {5.0 , 6.0 , 7.0 , 8.0 };
480+ matrix * blk1 = new_permuted_dense (4 , 4 , 2 , 2 , row_perm_1 , col_perm_1 , X1 );
481+
482+ permuted_dense * blocks [2 ] = {(permuted_dense * ) blk0 , (permuted_dense * ) blk1 };
483+ matrix * src_m = new_stacked_pd (4 , 4 , 2 , blocks , NULL , NULL );
484+
485+ matrix * out_m = transpose_spd_alloc ((stacked_pd * ) src_m );
486+ transpose_spd_fill_values ((stacked_pd * ) src_m , (stacked_pd * ) out_m );
487+ stacked_pd * out = (stacked_pd * ) out_m ;
488+
489+ mu_assert ("m" , out_m -> m == 4 );
490+ mu_assert ("n" , out_m -> n == 4 );
491+ mu_assert ("n_blocks" , out -> n_blocks == 2 );
492+ int expected_src_p [3 ] = {0 , 1 , 2 };
493+ int expected_src [2 ] = {0 , 1 };
494+ mu_assert ("src_block_idx_p" ,
495+ cmp_int_array (out -> src_block_idx_p , expected_src_p , 3 ));
496+ mu_assert ("src_block_idx" , cmp_int_array (out -> src_block_idx , expected_src , 2 ));
497+
498+ /* Transpose of [[1,2],[3,4]] is [[1,3],[2,4]] (row-major flatten:
499+ [1, 3, 2, 4]); similarly for the second block. */
500+ permuted_dense * O0 = out -> blocks [0 ];
501+ double O0_X_expected [4 ] = {1.0 , 3.0 , 2.0 , 4.0 };
502+ mu_assert ("O0 row_perm" , cmp_int_array (O0 -> row_perm , row_perm_0 , 2 ));
503+ mu_assert ("O0 col_perm" , cmp_int_array (O0 -> col_perm , col_perm_0 , 2 ));
504+ mu_assert ("O0 X" , cmp_double_array (O0 -> X , O0_X_expected , 4 ));
505+
506+ permuted_dense * O1 = out -> blocks [1 ];
507+ double O1_X_expected [4 ] = {5.0 , 7.0 , 6.0 , 8.0 };
508+ mu_assert ("O1 row_perm" , cmp_int_array (O1 -> row_perm , row_perm_1 , 2 ));
509+ mu_assert ("O1 col_perm" , cmp_int_array (O1 -> col_perm , col_perm_1 , 2 ));
510+ mu_assert ("O1 X" , cmp_double_array (O1 -> X , O1_X_expected , 4 ));
511+
512+ free_matrix (out_m );
513+ free_matrix (src_m );
514+ return 0 ;
515+ }
516+
517+ /* Transpose where source blocks share a column. After per-block
518+ transpose two raw blocks share a row, so coalesce merges and produces
519+ three output PDs (the worked example from the design conversation). */
520+ const char * test_transpose_spd_overlap_coalesces (void )
521+ {
522+ /* Source (4x5):
523+ block 0: rows {0, 1}, cols {2, 4}, X = [[1, 2], [3, 4]]
524+ block 1: rows {3}, cols {0, 2}, X = [10, 11]
525+ (Disjoint rows; cells {(0,2),(0,4),(1,2),(1,4)} and {(3,0),(3,2)}
526+ are disjoint.) */
527+ int row_perm_0 [2 ] = {0 , 1 };
528+ int col_perm_0 [2 ] = {2 , 4 };
529+ double X0 [4 ] = {1.0 , 2.0 , 3.0 , 4.0 };
530+ matrix * blk0 = new_permuted_dense (4 , 5 , 2 , 2 , row_perm_0 , col_perm_0 , X0 );
531+
532+ int row_perm_1 [1 ] = {3 };
533+ int col_perm_1 [2 ] = {0 , 2 };
534+ double X1 [2 ] = {10.0 , 11.0 };
535+ matrix * blk1 = new_permuted_dense (4 , 5 , 1 , 2 , row_perm_1 , col_perm_1 , X1 );
536+
537+ permuted_dense * blocks [2 ] = {(permuted_dense * ) blk0 , (permuted_dense * ) blk1 };
538+ matrix * src_m = new_stacked_pd (4 , 5 , 2 , blocks , NULL , NULL );
539+
540+ matrix * out_m = transpose_spd_alloc ((stacked_pd * ) src_m );
541+ transpose_spd_fill_values ((stacked_pd * ) src_m , (stacked_pd * ) out_m );
542+ stacked_pd * out = (stacked_pd * ) out_m ;
543+
544+ /* Transposed (5x4):
545+ raw block 0^T: rows {2, 4}, cols {0, 1}, X = [[1, 3], [2, 4]]
546+ raw block 1^T: rows {0, 2}, cols {3}, X = [10, 11]
547+ Row 2 shared. Coalesce produces (ordered by min row):
548+ sig {1}: row {0}, col {3}, X = [10]
549+ sig {0, 1}: row {2}, cols {0, 1, 3}, X = [1, 3, 11]
550+ sig {0}: row {4}, cols {0, 1}, X = [2, 4] */
551+ mu_assert ("m" , out_m -> m == 5 );
552+ mu_assert ("n" , out_m -> n == 4 );
553+ mu_assert ("n_blocks" , out -> n_blocks == 3 );
554+
555+ int expected_src_p [4 ] = {0 , 1 , 3 , 4 };
556+ int expected_src [4 ] = {1 , 0 , 1 , 0 };
557+ mu_assert ("src_block_idx_p" ,
558+ cmp_int_array (out -> src_block_idx_p , expected_src_p , 4 ));
559+ mu_assert ("src_block_idx" , cmp_int_array (out -> src_block_idx , expected_src , 4 ));
560+
561+ permuted_dense * O0 = out -> blocks [0 ];
562+ int O0_row [1 ] = {0 };
563+ int O0_col [1 ] = {3 };
564+ double O0_X [1 ] = {10.0 };
565+ mu_assert ("O0 row" , cmp_int_array (O0 -> row_perm , O0_row , 1 ));
566+ mu_assert ("O0 col" , cmp_int_array (O0 -> col_perm , O0_col , 1 ));
567+ mu_assert ("O0 X" , cmp_double_array (O0 -> X , O0_X , 1 ));
568+
569+ permuted_dense * O1 = out -> blocks [1 ];
570+ int O1_row [1 ] = {2 };
571+ int O1_col [3 ] = {0 , 1 , 3 };
572+ double O1_X [3 ] = {1.0 , 3.0 , 11.0 };
573+ mu_assert ("O1 row" , cmp_int_array (O1 -> row_perm , O1_row , 1 ));
574+ mu_assert ("O1 col" , cmp_int_array (O1 -> col_perm , O1_col , 3 ));
575+ mu_assert ("O1 X" , cmp_double_array (O1 -> X , O1_X , 3 ));
576+
577+ permuted_dense * O2 = out -> blocks [2 ];
578+ int O2_row [1 ] = {4 };
579+ int O2_col [2 ] = {0 , 1 };
580+ double O2_X [2 ] = {2.0 , 4.0 };
581+ mu_assert ("O2 row" , cmp_int_array (O2 -> row_perm , O2_row , 1 ));
582+ mu_assert ("O2 col" , cmp_int_array (O2 -> col_perm , O2_col , 2 ));
583+ mu_assert ("O2 X" , cmp_double_array (O2 -> X , O2_X , 2 ));
584+
585+ free_matrix (out_m );
586+ free_matrix (src_m );
587+ return 0 ;
588+ }
589+
590+ /* Two-phase: alloc once, mutate src values, refill, re-check. */
591+ const char * test_transpose_spd_alloc_then_fill_values (void )
592+ {
593+ int row_perm_0 [2 ] = {0 , 1 };
594+ int col_perm_0 [2 ] = {2 , 4 };
595+ double X0 [4 ] = {1.0 , 2.0 , 3.0 , 4.0 };
596+ matrix * blk0 = new_permuted_dense (4 , 5 , 2 , 2 , row_perm_0 , col_perm_0 , X0 );
597+
598+ int row_perm_1 [1 ] = {3 };
599+ int col_perm_1 [2 ] = {0 , 2 };
600+ double X1 [2 ] = {10.0 , 11.0 };
601+ matrix * blk1 = new_permuted_dense (4 , 5 , 1 , 2 , row_perm_1 , col_perm_1 , X1 );
602+
603+ permuted_dense * blocks [2 ] = {(permuted_dense * ) blk0 , (permuted_dense * ) blk1 };
604+ matrix * src_m = new_stacked_pd (4 , 5 , 2 , blocks , NULL , NULL );
605+ stacked_pd * src = (stacked_pd * ) src_m ;
606+
607+ matrix * out_m = transpose_spd_alloc (src );
608+ transpose_spd_fill_values (src , (stacked_pd * ) out_m );
609+
610+ /* Mutate src values then refill:
611+ blk0 X -> [100, 200, 300, 400]
612+ blk1 X -> [50, 60] */
613+ src -> blocks [0 ]-> X [0 ] = 100.0 ;
614+ src -> blocks [0 ]-> X [1 ] = 200.0 ;
615+ src -> blocks [0 ]-> X [2 ] = 300.0 ;
616+ src -> blocks [0 ]-> X [3 ] = 400.0 ;
617+ src -> blocks [1 ]-> X [0 ] = 50.0 ;
618+ src -> blocks [1 ]-> X [1 ] = 60.0 ;
619+
620+ transpose_spd_fill_values (src , (stacked_pd * ) out_m );
621+ stacked_pd * out = (stacked_pd * ) out_m ;
622+
623+ /* Expected after refill:
624+ O0: X = [50]
625+ O1: X = [100, 300, 60]
626+ O2: X = [200, 400] */
627+ double O0_X [1 ] = {50.0 };
628+ mu_assert ("O0 X refilled" , cmp_double_array (out -> blocks [0 ]-> X , O0_X , 1 ));
629+ double O1_X [3 ] = {100.0 , 300.0 , 60.0 };
630+ mu_assert ("O1 X refilled" , cmp_double_array (out -> blocks [1 ]-> X , O1_X , 3 ));
631+ double O2_X [2 ] = {200.0 , 400.0 };
632+ mu_assert ("O2 X refilled" , cmp_double_array (out -> blocks [2 ]-> X , O2_X , 2 ));
633+
634+ free_matrix (out_m );
635+ free_matrix (src_m );
636+ return 0 ;
637+ }
638+
639+ /* Empty source: output also empty; work is an empty spd; free safe. */
640+ const char * test_transpose_spd_empty (void )
641+ {
642+ matrix * src_m = new_stacked_pd (4 , 5 , 0 , NULL , NULL , NULL );
643+ matrix * out_m = transpose_spd_alloc ((stacked_pd * ) src_m );
644+ transpose_spd_fill_values ((stacked_pd * ) src_m , (stacked_pd * ) out_m );
645+ stacked_pd * out = (stacked_pd * ) out_m ;
646+
647+ mu_assert ("m" , out_m -> m == 5 );
648+ mu_assert ("n" , out_m -> n == 4 );
649+ mu_assert ("n_blocks" , out -> n_blocks == 0 );
650+ mu_assert ("nnz" , out_m -> nnz == 0 );
651+ mu_assert ("work not NULL" , out -> work != NULL );
652+ mu_assert ("work n_blocks" , out -> work -> n_blocks == 0 );
653+
654+ free_matrix (out_m );
655+ free_matrix (src_m );
656+ return 0 ;
657+ }
658+
659+ /* Single full-dense block: trivial case, just a dense transpose. */
660+ const char * test_transpose_spd_single_block_full (void )
661+ {
662+ /* 2x3 dense block, X = [[1, 2, 3], [4, 5, 6]] */
663+ double data [6 ] = {1.0 , 2.0 , 3.0 , 4.0 , 5.0 , 6.0 };
664+ matrix * blk = new_permuted_dense_full (2 , 3 , data );
665+
666+ permuted_dense * blocks [1 ] = {(permuted_dense * ) blk };
667+ matrix * src_m = new_stacked_pd (2 , 3 , 1 , blocks , NULL , NULL );
668+
669+ matrix * out_m = transpose_spd_alloc ((stacked_pd * ) src_m );
670+ transpose_spd_fill_values ((stacked_pd * ) src_m , (stacked_pd * ) out_m );
671+ stacked_pd * out = (stacked_pd * ) out_m ;
672+
673+ mu_assert ("m" , out_m -> m == 3 );
674+ mu_assert ("n" , out_m -> n == 2 );
675+ mu_assert ("n_blocks" , out -> n_blocks == 1 );
676+
677+ /* Transposed 3x2 dense: [[1, 4], [2, 5], [3, 6]] -> [1, 4, 2, 5, 3, 6]. */
678+ permuted_dense * O = out -> blocks [0 ];
679+ int O_row [3 ] = {0 , 1 , 2 };
680+ int O_col [2 ] = {0 , 1 };
681+ double O_X [6 ] = {1.0 , 4.0 , 2.0 , 5.0 , 3.0 , 6.0 };
682+ mu_assert ("O m0" , O -> m0 == 3 );
683+ mu_assert ("O n0" , O -> n0 == 2 );
684+ mu_assert ("O row" , cmp_int_array (O -> row_perm , O_row , 3 ));
685+ mu_assert ("O col" , cmp_int_array (O -> col_perm , O_col , 2 ));
686+ mu_assert ("O X" , cmp_double_array (O -> X , O_X , 6 ));
687+
688+ free_matrix (out_m );
689+ free_matrix (src_m );
690+ return 0 ;
691+ }
692+
465693#endif /* TEST_STACKED_PD_H */
0 commit comments