Skip to content

Commit 896c8b0

Browse files
authored
Route convolve's Jacobian through the BA_matrices dispatchers (#157)
1 parent bef523d commit 896c8b0

4 files changed

Lines changed: 106 additions & 21 deletions

File tree

‎include/subexpr.h‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,7 @@ typedef struct convolve_expr
165165
expr *param_source; /* length-m kernel */
166166
int m; /* kernel length */
167167
int n; /* input length */
168-
CSR_matrix *T; /* (m+n-1) x n convolution matrix */
169-
CSC_matrix *Jchild_CSC;
168+
matrix *T; /* (m+n-1) x n convolution matrix (sparse) */
170169
} convolve_expr;
171170

172171
/* Kronecker product Z = kron(A, B) where one operand is variable-free (held by

‎src/atoms/affine/convolve.c‎

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "atoms/affine.h"
1919
#include "sparsediff_linalg/CSR_matrix.h"
2020
#include "sparsediff_linalg/csr_kernels.h"
21+
#include "sparsediff_linalg/matmul.h"
2122
#include "sparsediff_linalg/matrix.h"
2223
#include "sparsediff_linalg/tracked_alloc.h"
2324
#include "subexpr.h"
@@ -44,7 +45,9 @@ static void forward(expr *node, const double *u)
4445
which might happen when we expose Python bindings to SparseDiffEngine) */
4546
if (cnode->T != NULL)
4647
{
47-
conv_matrix_fill_values(cnode->T, cnode->param_source->value);
48+
conv_matrix_fill_values(cnode->T->to_csr(cnode->T),
49+
cnode->param_source->value);
50+
matrix_values_changed(cnode->T);
4851
}
4952
cnode->base.needs_parameter_refresh = false;
5053
}
@@ -76,15 +79,14 @@ static void jacobian_init_impl(expr *node)
7679
jacobian_init(child);
7780

7881
/* Build convolution matrix of size (m+n-1) x n with m*n nonzeros */
79-
cnode->T = new_CSR_matrix(m + n - 1, n, m * n);
80-
conv_matrix_fill_sparsity(cnode->T, m, n);
81-
conv_matrix_fill_values(cnode->T, a);
82-
83-
/* J = T @ J_child */
84-
cnode->Jchild_CSC = csr_to_csc_alloc(child->jacobian->to_csr(child->jacobian),
85-
node->work->iwork);
86-
node->jacobian =
87-
new_sparse_matrix(csr_csc_matmul_alloc(cnode->T, cnode->Jchild_CSC));
82+
CSR_matrix *T = new_CSR_matrix(m + n - 1, n, m * n);
83+
conv_matrix_fill_sparsity(T, m, n);
84+
conv_matrix_fill_values(T, a);
85+
cnode->T = new_sparse_matrix(T);
86+
87+
/* J = T @ J_child; its kind follows the child's (sparse, permuted_dense or
88+
stacked_pd) */
89+
node->jacobian = BA_matrices_alloc(cnode->T, child->jacobian);
8890
}
8991

9092
static void eval_jacobian_impl(expr *node)
@@ -95,10 +97,7 @@ static void eval_jacobian_impl(expr *node)
9597
eval_jacobian(child);
9698

9799
/* J = T @ J_child */
98-
csr_to_csc_fill_values(child->jacobian->to_csr(child->jacobian),
99-
cnode->Jchild_CSC, node->work->iwork);
100-
csr_csc_matmul_fill_values(cnode->T, cnode->Jchild_CSC,
101-
node->jacobian->to_csr(node->jacobian));
100+
BA_matrices_fill_values(cnode->T, child->jacobian, node->jacobian);
102101
}
103102

104103
static void wsum_hess_init_impl(expr *node)
@@ -149,8 +148,7 @@ static bool set_needs_refresh_param_source(expr *node)
149148
static void free_type_data(expr *node)
150149
{
151150
convolve_expr *cnode = (convolve_expr *) node;
152-
free_CSR_matrix(cnode->T);
153-
free_CSC_matrix(cnode->Jchild_CSC);
151+
free_matrix(cnode->T);
154152
free_expr(cnode->param_source);
155153
}
156154

@@ -194,9 +192,6 @@ expr *new_convolve(expr *param_node, expr *child)
194192
cnode->m = m;
195193
cnode->n = n;
196194

197-
/* iwork is used for csr_to_csc of child's Jacobian (size n_vars). */
198-
node->work->iwork = (int *) sp_malloc(node->n_vars * sizeof(int));
199-
200195
/* Ensure first forward() pulls current param values through any
201196
broadcast/promote wrappers and reflects them in T (once T is built). */
202197
cnode->base.needs_parameter_refresh = true;

‎tests/all_tests.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ int main(void)
266266
mu_run_test(test_jacobian_matmul, tests_run);
267267
mu_run_test(test_jacobian_convolve, tests_run);
268268
mu_run_test(test_jacobian_convolve_composite, tests_run);
269+
mu_run_test(test_jacobian_convolve_csc_consumer, tests_run);
270+
mu_run_test(test_jacobian_convolve_pd_child, tests_run);
271+
mu_run_test(test_jacobian_convolve_spd_child, tests_run);
269272
mu_run_test(test_jacobian_kron_const_left, tests_run);
270273
mu_run_test(test_jacobian_kron_const_right, tests_run);
271274
mu_run_test(test_jacobian_kron_sparse, tests_run);

‎tests/jacobian_tests/affine/test_convolve.h‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#include <math.h>
22
#include <stdio.h>
3+
#include <string.h>
34

45
#include "atoms/affine.h"
6+
#include "atoms/elementwise_full_dom.h"
57
#include "atoms/elementwise_restricted_dom.h"
68
#include "expr.h"
79
#include "minunit.h"
@@ -63,3 +65,89 @@ const char *test_jacobian_convolve_composite(void)
6365
free_expr(y);
6466
return 0;
6567
}
68+
69+
const char *test_jacobian_convolve_csc_consumer(void)
70+
{
71+
/* y = B @ conv([1, 2, 3], exp(x)) with B a sparse CSR: the left matmul
72+
* (n_blocks = 1) reads conv's Jacobian through its CSC mirror. Checking at
73+
* two points catches a mirror left stale by the second evaluation. */
74+
double kernel[3] = {1.0, 2.0, 3.0};
75+
double u1[3] = {0.1, 0.2, 0.3};
76+
double u2[3] = {-0.4, 0.5, 1.0};
77+
78+
CSR_matrix *B = new_CSR_matrix(2, 5, 5);
79+
int Bp[3] = {0, 3, 5};
80+
int Bi[5] = {0, 2, 4, 1, 3};
81+
double Bx[5] = {1.0, -2.0, 0.5, 3.0, 1.5};
82+
memcpy(B->p, Bp, 3 * sizeof(int));
83+
memcpy(B->i, Bi, 5 * sizeof(int));
84+
memcpy(B->x, Bx, 5 * sizeof(double));
85+
86+
expr *kernel_param = new_parameter(3, 1, PARAM_FIXED, 3, kernel);
87+
expr *x = new_variable(3, 1, 0, 3);
88+
expr *conv = new_convolve(kernel_param, new_exp(x));
89+
expr *y = new_left_matmul(NULL, conv, B);
90+
91+
mu_assert("check_jacobian failed at u1",
92+
check_jacobian_num(y, u1, NUMERICAL_DIFF_DEFAULT_H));
93+
mu_assert("check_jacobian failed at u2",
94+
check_jacobian_num(y, u2, NUMERICAL_DIFF_DEFAULT_H));
95+
96+
free_expr(y);
97+
free_CSR_matrix(B);
98+
return 0;
99+
}
100+
101+
const char *test_jacobian_convolve_pd_child(void)
102+
{
103+
/* y = conv([1, 2, 3], exp(D @ x)) with D dense: the child Jacobian is
104+
* permuted_dense, and so is conv's. */
105+
double kernel[3] = {1.0, 2.0, 3.0};
106+
double D[12] = {1.0, -0.5, 2.0, 0.5, 1.5, -1.0, 0.3, 0.2, -0.7, -1.2, 0.4, 0.9};
107+
double u[3] = {0.1, -0.2, 0.3};
108+
109+
expr *kernel_param = new_parameter(3, 1, PARAM_FIXED, 3, kernel);
110+
expr *x = new_variable(3, 1, 0, 3);
111+
expr *child = new_exp(new_left_matmul_dense(NULL, x, 4, 3, D));
112+
expr *y = new_convolve(kernel_param, child);
113+
114+
jacobian_init(y);
115+
mu_assert("child jacobian must be permuted_dense",
116+
child->jacobian->kind == MATRIX_PERMUTED_DENSE);
117+
mu_assert("jacobian must be permuted_dense",
118+
y->jacobian->kind == MATRIX_PERMUTED_DENSE);
119+
mu_assert("check_jacobian failed",
120+
check_jacobian_num(y, u, NUMERICAL_DIFF_DEFAULT_H));
121+
122+
free_expr(y);
123+
return 0;
124+
}
125+
126+
const char *test_jacobian_convolve_spd_child(void)
127+
{
128+
/* y = conv([1, 2, 3], exp(vec(A @ X))) with X a 3x2 matrix variable: the
129+
* dense left matmul has n_blocks = 2, so the child Jacobian is stacked_pd,
130+
* and so is conv's. */
131+
double kernel[3] = {1.0, 2.0, 3.0};
132+
double A[6] = {1.0, -0.5, 2.0, 0.5, 1.5, -1.0};
133+
double u1[6] = {0.1, 0.2, 0.3, -0.1, -0.2, -0.3};
134+
double u2[6] = {0.4, -0.3, 0.2, 0.1, 0.3, -0.4};
135+
136+
expr *kernel_param = new_parameter(3, 1, PARAM_FIXED, 6, kernel);
137+
expr *X = new_variable(3, 2, 0, 6);
138+
expr *child =
139+
new_exp(new_reshape(new_left_matmul_dense(NULL, X, 2, 3, A), 4, 1));
140+
expr *y = new_convolve(kernel_param, child);
141+
142+
jacobian_init(y);
143+
mu_assert("child jacobian must be stacked_pd",
144+
child->jacobian->kind == MATRIX_STACKED_PD);
145+
mu_assert("jacobian must be stacked_pd", y->jacobian->kind == MATRIX_STACKED_PD);
146+
mu_assert("check_jacobian failed at u1",
147+
check_jacobian_num(y, u1, NUMERICAL_DIFF_DEFAULT_H));
148+
mu_assert("check_jacobian failed at u2",
149+
check_jacobian_num(y, u2, NUMERICAL_DIFF_DEFAULT_H));
150+
151+
free_expr(y);
152+
return 0;
153+
}

0 commit comments

Comments
 (0)