Skip to content

Commit 369c7a9

Browse files
authored
Merge pull request #52 from SparseDifferentiation/vstack
[Ready for review] Add vstack atom through transpose(hstack(transpose[args]))
2 parents d3a5747 + 045f86f commit 369c7a9

6 files changed

Lines changed: 352 additions & 0 deletions

File tree

include/affine.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ expr *new_neg(expr *child);
2929

3030
expr *new_sum(expr *child, int axis);
3131
expr *new_hstack(expr **args, int n_args, int n_vars);
32+
expr *new_vstack(expr **args, int n_args, int n_vars);
3233
expr *new_promote(expr *child, int d1, int d2);
3334
expr *new_trace(expr *child);
3435

src/affine/vstack.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2026 Daniel Cederberg and William Zhang
3+
*
4+
* This file is part of the DNLP-differentiation-engine project.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
#include "affine.h"
19+
#include <assert.h>
20+
#include <stdlib.h>
21+
22+
/*
23+
* vstack(args) = transpose(hstack(transpose(args[0]),
24+
* transpose(args[1]),
25+
* ...))
26+
*
27+
* All args must share the same d2 (number of columns).
28+
*/
29+
expr *new_vstack(expr **args, int n_args, int n_vars)
30+
{
31+
assert(n_args > 0);
32+
for (int i = 1; i < n_args; i++)
33+
{
34+
assert(args[i]->d2 == args[0]->d2);
35+
}
36+
37+
expr **transposed = (expr **) malloc(n_args * sizeof(expr *));
38+
for (int i = 0; i < n_args; i++)
39+
{
40+
transposed[i] = new_transpose(args[i]);
41+
}
42+
43+
expr *hstacked = new_hstack(transposed, n_args, n_vars);
44+
free(transposed);
45+
46+
return new_transpose(hstacked);
47+
}

tests/all_tests.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "forward_pass/affine/test_add.h"
88
#include "forward_pass/affine/test_broadcast.h"
99
#include "forward_pass/affine/test_hstack.h"
10+
#include "forward_pass/affine/test_vstack.h"
1011
#include "forward_pass/affine/test_linear_op.h"
1112
#include "forward_pass/affine/test_neg.h"
1213
#include "forward_pass/affine/test_promote.h"
@@ -26,6 +27,7 @@
2627
#include "jacobian_tests/test_const_vector_mult.h"
2728
#include "jacobian_tests/test_elementwise_mult.h"
2829
#include "jacobian_tests/test_hstack.h"
30+
#include "jacobian_tests/test_vstack.h"
2931
#include "jacobian_tests/test_index.h"
3032
#include "jacobian_tests/test_left_matmul.h"
3133
#include "jacobian_tests/test_log.h"
@@ -64,6 +66,7 @@
6466
#include "wsum_hess/test_const_scalar_mult.h"
6567
#include "wsum_hess/test_const_vector_mult.h"
6668
#include "wsum_hess/test_hstack.h"
69+
#include "wsum_hess/test_vstack.h"
6770
#include "wsum_hess/test_index.h"
6871
#include "wsum_hess/test_left_matmul.h"
6972
#include "wsum_hess/test_matmul.h"
@@ -109,6 +112,8 @@ int main(void)
109112
mu_run_test(test_sum_axis_1, tests_run);
110113
mu_run_test(test_hstack_forward_vectors, tests_run);
111114
mu_run_test(test_hstack_forward_matrix, tests_run);
115+
mu_run_test(test_vstack_forward_vectors, tests_run);
116+
mu_run_test(test_vstack_forward_matrix, tests_run);
112117
mu_run_test(test_broadcast_row, tests_run);
113118
mu_run_test(test_broadcast_col, tests_run);
114119
mu_run_test(test_broadcast_matrix, tests_run);
@@ -158,6 +163,8 @@ int main(void)
158163
mu_run_test(test_jacobian_sum_log_axis_1, tests_run);
159164
mu_run_test(test_jacobian_hstack_vectors, tests_run);
160165
mu_run_test(test_jacobian_hstack_matrix, tests_run);
166+
mu_run_test(test_jacobian_vstack_vectors, tests_run);
167+
mu_run_test(test_jacobian_vstack_matrix, tests_run);
161168
mu_run_test(test_index_forward_simple, tests_run);
162169
mu_run_test(test_index_forward_repeated, tests_run);
163170
mu_run_test(test_index_jacobian_of_variable, tests_run);
@@ -218,6 +225,8 @@ int main(void)
218225
mu_run_test(test_wsum_hess_rel_entr_scalar_vector, tests_run);
219226
mu_run_test(test_wsum_hess_hstack, tests_run);
220227
mu_run_test(test_wsum_hess_hstack_matrix, tests_run);
228+
mu_run_test(test_wsum_hess_vstack_vectors, tests_run);
229+
mu_run_test(test_wsum_hess_vstack_matrix, tests_run);
221230
mu_run_test(test_wsum_hess_index_log, tests_run);
222231
mu_run_test(test_wsum_hess_index_repeated, tests_run);
223232
mu_run_test(test_wsum_hess_sum_index_log, tests_run);
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include <math.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
5+
#include "affine.h"
6+
#include "elementwise_univariate.h"
7+
#include "expr.h"
8+
#include "minunit.h"
9+
#include "test_helpers.h"
10+
11+
const char *test_vstack_forward_vectors(void)
12+
{
13+
/* vstack([log(x), exp(x)]) where x is (3,1)
14+
* Output is (6,1): [log(1), log(2), log(3), exp(1), exp(2), exp(3)]
15+
* For d2=1, vstack == hstack (no interleaving needed)
16+
*/
17+
double u[3] = {1.0, 2.0, 3.0};
18+
expr *x = new_variable(3, 1, 0, 3);
19+
20+
expr *log_x = new_log(x);
21+
expr *exp_x = new_exp(x);
22+
23+
expr *args[2] = {log_x, exp_x};
24+
expr *stack = new_vstack(args, 2, 3);
25+
26+
mu_assert("vstack vectors: wrong d1", stack->d1 == 6);
27+
mu_assert("vstack vectors: wrong d2", stack->d2 == 1);
28+
29+
stack->forward(stack, u);
30+
31+
double expected[6] = {log(1.0), log(2.0), log(3.0),
32+
exp(1.0), exp(2.0), exp(3.0)};
33+
34+
mu_assert("vstack forward vectors failed",
35+
cmp_double_array(stack->value, expected, 6));
36+
37+
free_expr(stack);
38+
return 0;
39+
}
40+
41+
const char *test_vstack_forward_matrix(void)
42+
{
43+
/* vstack([log(x), exp(y)]) where x is (2,3), y is (1,3)
44+
* x stored column-wise: [1, 2, 3, 4, 5, 6]
45+
* x = [1 3 5]
46+
* [2 4 6]
47+
* y stored column-wise: [7, 8, 9]
48+
* y = [7 8 9]
49+
*
50+
* Output is (3,3), stored column-wise:
51+
* result = [log(1) log(3) log(5)]
52+
* [log(2) log(4) log(6)]
53+
* [exp(7) exp(8) exp(9)]
54+
*
55+
* Flat column-wise:
56+
* [log(1), log(2), exp(7), log(3), log(4), exp(8),
57+
* log(5), log(6), exp(9)]
58+
*/
59+
double u[9] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};
60+
61+
expr *x = new_variable(2, 3, 0, 9);
62+
expr *y = new_variable(1, 3, 6, 9);
63+
64+
expr *log_x = new_log(x);
65+
expr *exp_y = new_exp(y);
66+
67+
expr *args[2] = {log_x, exp_y};
68+
expr *stack = new_vstack(args, 2, 9);
69+
70+
mu_assert("vstack matrix: wrong d1", stack->d1 == 3);
71+
mu_assert("vstack matrix: wrong d2", stack->d2 == 3);
72+
73+
stack->forward(stack, u);
74+
75+
double expected[9] = {log(1.0), log(2.0), exp(7.0), log(3.0), log(4.0),
76+
exp(8.0), log(5.0), log(6.0), exp(9.0)};
77+
78+
mu_assert("vstack forward matrix failed",
79+
cmp_double_array(stack->value, expected, 9));
80+
81+
free_expr(stack);
82+
return 0;
83+
}

tests/jacobian_tests/test_vstack.h

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#include <math.h>
2+
#include <stdio.h>
3+
4+
#include "affine.h"
5+
#include "elementwise_univariate.h"
6+
#include "expr.h"
7+
#include "minunit.h"
8+
#include "test_helpers.h"
9+
10+
const char *test_jacobian_vstack_vectors(void)
11+
{
12+
/* vstack([log(x), exp(x)]) where x is (3,1)
13+
* Output (6,1) flat: [log(1), log(2), log(3), exp(1), exp(2), exp(3)]
14+
*
15+
* Jacobian is 6x3:
16+
* row 0: d(log(x0))/dx0 = 1/1 at col 0
17+
* row 1: d(log(x1))/dx1 = 1/2 at col 1
18+
* row 2: d(log(x2))/dx2 = 1/3 at col 2
19+
* row 3: d(exp(x0))/dx0 = e^1 at col 0
20+
* row 4: d(exp(x1))/dx1 = e^2 at col 1
21+
* row 5: d(exp(x2))/dx2 = e^3 at col 2
22+
*/
23+
double u[3] = {1.0, 2.0, 3.0};
24+
expr *x = new_variable(3, 1, 0, 3);
25+
26+
expr *log_x = new_log(x);
27+
expr *exp_x = new_exp(x);
28+
29+
expr *args[2] = {log_x, exp_x};
30+
expr *stack = new_vstack(args, 2, 3);
31+
32+
stack->forward(stack, u);
33+
stack->jacobian_init(stack);
34+
stack->eval_jacobian(stack);
35+
36+
double expected_x[6] = {1.0, 0.5, 1.0 / 3.0, exp(1.0), exp(2.0), exp(3.0)};
37+
int expected_i[6] = {0, 1, 2, 0, 1, 2};
38+
int expected_p[7] = {0, 1, 2, 3, 4, 5, 6};
39+
40+
mu_assert("vstack jac vectors: vals",
41+
cmp_double_array(stack->jacobian->x, expected_x, 6));
42+
mu_assert("vstack jac vectors: cols",
43+
cmp_int_array(stack->jacobian->i, expected_i, 6));
44+
mu_assert("vstack jac vectors: rows",
45+
cmp_int_array(stack->jacobian->p, expected_p, 7));
46+
47+
free_expr(stack);
48+
return 0;
49+
}
50+
51+
const char *test_jacobian_vstack_matrix(void)
52+
{
53+
/* vstack([log(x), exp(y)]) where x is (2,3), y is (1,3)
54+
* x at var_id 0 (6 vars), y at var_id 6 (3 vars), total 9 vars
55+
*
56+
* Output (3,3) flat column-wise:
57+
* [log(x0), log(x1), exp(y0), log(x2), log(x3), exp(y1),
58+
* log(x4), log(x5), exp(y2)]
59+
*
60+
* Jacobian is 9x9 sparse:
61+
* row 0 (log(x0)): 1/x0 at col 0
62+
* row 1 (log(x1)): 1/x1 at col 1
63+
* row 2 (exp(y0)): e^y0 at col 6
64+
* row 3 (log(x2)): 1/x2 at col 2
65+
* row 4 (log(x3)): 1/x3 at col 3
66+
* row 5 (exp(y1)): e^y1 at col 7
67+
* row 6 (log(x4)): 1/x4 at col 4
68+
* row 7 (log(x5)): 1/x5 at col 5
69+
* row 8 (exp(y2)): e^y2 at col 8
70+
*/
71+
double u[9] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};
72+
expr *x = new_variable(2, 3, 0, 9);
73+
expr *y = new_variable(1, 3, 6, 9);
74+
75+
expr *log_x = new_log(x);
76+
expr *exp_y = new_exp(y);
77+
78+
expr *args[2] = {log_x, exp_y};
79+
expr *stack = new_vstack(args, 2, 9);
80+
81+
stack->forward(stack, u);
82+
stack->jacobian_init(stack);
83+
stack->eval_jacobian(stack);
84+
85+
double expected_x[9] = {1.0, 0.5, exp(7.0), 1.0 / 3.0, 0.25,
86+
exp(8.0), 0.2, 1.0 / 6.0, exp(9.0)};
87+
int expected_i[9] = {0, 1, 6, 2, 3, 7, 4, 5, 8};
88+
int expected_p[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
89+
90+
mu_assert("vstack jac matrix: vals",
91+
cmp_double_array(stack->jacobian->x, expected_x, 9));
92+
mu_assert("vstack jac matrix: cols",
93+
cmp_int_array(stack->jacobian->i, expected_i, 9));
94+
mu_assert("vstack jac matrix: rows",
95+
cmp_int_array(stack->jacobian->p, expected_p, 10));
96+
97+
free_expr(stack);
98+
return 0;
99+
}

0 commit comments

Comments
 (0)