Skip to content

Commit 06a9ece

Browse files
committed
first draft of transpose
1 parent 2b64360 commit 06a9ece

4 files changed

Lines changed: 292 additions & 0 deletions

File tree

‎include/utils/stacked_pd.h‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ typedef struct stacked_pd
3939
permuted_dense **blocks; /* owned; length n_blocks */
4040
int *src_block_idx_p; /* owned; length n_blocks + 1 */
4141
int *src_block_idx; /* owned; length src_block_idx_p[n_blocks] */
42+
struct stacked_pd *work; /* owned; NULL except when this spd was */
43+
/* produced by an op that needs a pre-coalesce */
44+
/* intermediate (currently transpose). When */
45+
/* set, holds the raw spd that */
46+
/* coalesce_spd_fill_values reads from. */
4247
} stacked_pd;
4348

4449
/* Constructor.
@@ -100,4 +105,16 @@ matrix *coalesce_spd_alloc(const stacked_pd *src);
100105
(block count, row_perms, col_perms) as the one passed now. */
101106
void coalesce_spd_fill_values(const stacked_pd *src, stacked_pd *out);
102107

108+
/* Allocate out = transpose(src). Implementation: transpose each source
109+
PD block individually (yielding a raw spd that may have overlapping
110+
row perms but pairwise-disjoint cells, by the spd invariant), then
111+
coalesce. The raw spd is stored on `out->work` for reuse by
112+
`transpose_spd_fill_values`. */
113+
matrix *transpose_spd_alloc(const stacked_pd *src);
114+
115+
/* Fill values of out = transpose(src). The caller must have produced
116+
`out` via transpose_spd_alloc on a `src` with the same structure
117+
(block count, row_perms, col_perms) as the one passed now. */
118+
void transpose_spd_fill_values(const stacked_pd *src, stacked_pd *out);
119+
103120
#endif /* STACKED_PD_H */

‎src/utils/stacked_pd.c‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ static void stacked_pd_free(matrix *self)
3636
free(spd->blocks);
3737
free(spd->src_block_idx_p);
3838
free(spd->src_block_idx);
39+
if (spd->work != NULL)
40+
{
41+
free_matrix((matrix *) spd->work);
42+
}
3943
free(spd);
4044
}
4145

@@ -488,3 +492,41 @@ void coalesce_spd_fill_values(const stacked_pd *src, stacked_pd *out)
488492
}
489493
}
490494
}
495+
496+
/* ------------------------------------------------------------------ */
497+
/* Transpose */
498+
/* ------------------------------------------------------------------ */
499+
500+
matrix *transpose_spd_alloc(const stacked_pd *src)
501+
{
502+
int n_blocks = src->n_blocks;
503+
permuted_dense **raw_blocks = (permuted_dense **) SP_MALLOC(
504+
(n_blocks > 0 ? n_blocks : 1) * sizeof(permuted_dense *));
505+
for (int k = 0; k < n_blocks; k++)
506+
{
507+
matrix *blk = (matrix *) src->blocks[k];
508+
raw_blocks[k] = (permuted_dense *) blk->transpose_alloc(blk);
509+
}
510+
511+
/* Raw spd: dimensions swapped, identity src_block_idx_*; rows may
512+
overlap so use the unchecked constructor. */
513+
matrix *raw = new_stacked_pd_unchecked(src->base.n, src->base.m, n_blocks,
514+
raw_blocks, NULL, NULL);
515+
free(raw_blocks);
516+
517+
matrix *out = coalesce_spd_alloc((stacked_pd *) raw);
518+
((stacked_pd *) out)->work = (stacked_pd *) raw;
519+
return out;
520+
}
521+
522+
void transpose_spd_fill_values(const stacked_pd *src, stacked_pd *out)
523+
{
524+
stacked_pd *raw = out->work;
525+
for (int k = 0; k < src->n_blocks; k++)
526+
{
527+
matrix *src_blk = (matrix *) src->blocks[k];
528+
matrix *raw_blk = (matrix *) raw->blocks[k];
529+
src_blk->transpose_fill_values(src_blk, raw_blk);
530+
}
531+
coalesce_spd_fill_values(raw, out);
532+
}

‎tests/all_tests.c‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,11 @@ int main(void)
413413
mu_run_test(test_coalesce_shared_signature_merges_rows, tests_run);
414414
mu_run_test(test_coalesce_alloc_then_fill_values, tests_run);
415415
mu_run_test(test_coalesce_empty_input, tests_run);
416+
mu_run_test(test_transpose_spd_no_overlap, tests_run);
417+
mu_run_test(test_transpose_spd_overlap_coalesces, tests_run);
418+
mu_run_test(test_transpose_spd_alloc_then_fill_values, tests_run);
419+
mu_run_test(test_transpose_spd_empty, tests_run);
420+
mu_run_test(test_transpose_spd_single_block_full, tests_run);
416421
mu_run_test(test_YT_kron_I, tests_run);
417422
mu_run_test(test_YT_kron_I_larger, tests_run);
418423
mu_run_test(test_I_kron_X, tests_run);

‎tests/utils/test_stacked_pd.h‎

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)