Skip to content

Commit 5139dce

Browse files
committed
jacobian checker of expression
1 parent 369c7a9 commit 5139dce

6 files changed

Lines changed: 159 additions & 6 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ if(NOT SKBUILD)
103103
add_executable(all_tests
104104
tests/all_tests.c
105105
tests/test_helpers.c
106+
tests/numerical_diff.c
106107
)
107108
target_link_libraries(all_tests dnlp_diff)
108109

src/utils/dense_matrix.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,16 @@ static void dense_block_left_mult_values(const Matrix *A, const CSC_Matrix *J,
156156
}
157157
else
158158
{
159-
/* scatter sparse J col into dense vector and then compute A @ j_dense */
159+
/* scatter sparse J col into dense vector and then compute A @
160+
* j_dense */
160161
memset(j_dense, 0, n * sizeof(double));
161162
for (s = start; s < end; s++)
162163
{
163164
j_dense[J->i[s] - block_start] = J->x[s];
164165
}
165166

166-
cblas_dgemv(CblasRowMajor, CblasNoTrans, m, n, 1.0, dm->x, n, j_dense, 1,
167-
0.0, C->x + i, 1);
167+
cblas_dgemv(CblasRowMajor, CblasNoTrans, m, n, 1.0, dm->x, n,
168+
j_dense, 1, 0.0, C->x + i, 1);
168169
}
169170
}
170171
}

tests/all_tests.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
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"
1110
#include "forward_pass/affine/test_linear_op.h"
1211
#include "forward_pass/affine/test_neg.h"
1312
#include "forward_pass/affine/test_promote.h"
1413
#include "forward_pass/affine/test_sum.h"
1514
#include "forward_pass/affine/test_variable_constant.h"
15+
#include "forward_pass/affine/test_vstack.h"
1616
#include "forward_pass/composite/test_composite.h"
1717
#include "forward_pass/elementwise/test_exp.h"
1818
#include "forward_pass/elementwise/test_log.h"
@@ -27,7 +27,6 @@
2727
#include "jacobian_tests/test_const_vector_mult.h"
2828
#include "jacobian_tests/test_elementwise_mult.h"
2929
#include "jacobian_tests/test_hstack.h"
30-
#include "jacobian_tests/test_vstack.h"
3130
#include "jacobian_tests/test_index.h"
3231
#include "jacobian_tests/test_left_matmul.h"
3332
#include "jacobian_tests/test_log.h"
@@ -46,6 +45,8 @@
4645
#include "jacobian_tests/test_sum.h"
4746
#include "jacobian_tests/test_trace.h"
4847
#include "jacobian_tests/test_transpose.h"
48+
#include "jacobian_tests/test_vstack.h"
49+
#include "numerical_diff/test_numerical_diff.h"
4950
#include "problem/test_problem.h"
5051
#include "utils/test_cblas.h"
5152
#include "utils/test_coo_matrix.h"
@@ -66,7 +67,6 @@
6667
#include "wsum_hess/test_const_scalar_mult.h"
6768
#include "wsum_hess/test_const_vector_mult.h"
6869
#include "wsum_hess/test_hstack.h"
69-
#include "wsum_hess/test_vstack.h"
7070
#include "wsum_hess/test_index.h"
7171
#include "wsum_hess/test_left_matmul.h"
7272
#include "wsum_hess/test_matmul.h"
@@ -83,6 +83,7 @@
8383
#include "wsum_hess/test_sum.h"
8484
#include "wsum_hess/test_trace.h"
8585
#include "wsum_hess/test_transpose.h"
86+
#include "wsum_hess/test_vstack.h"
8687
#endif /* PROFILE_ONLY */
8788

8889
#ifdef PROFILE_ONLY
@@ -297,6 +298,9 @@ int main(void)
297298
mu_run_test(test_dense_matrix_trans, tests_run);
298299
mu_run_test(test_sparse_vs_dense_mult_vec_blocks, tests_run);
299300

301+
printf("\n--- Numerical Diff Tests ---\n");
302+
mu_run_test(test_check_jacobian_composite_log, tests_run);
303+
300304
printf("\n--- Problem Struct Tests ---\n");
301305
mu_run_test(test_problem_new_free, tests_run);
302306
mu_run_test(test_problem_objective_forward, tests_run);

tests/numerical_diff.c

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+
#include <stdlib.h>
4+
#include <string.h>
5+
6+
#include "numerical_diff.h"
7+
8+
#define ABS_TOL 1e-6
9+
#define REL_TOL 1e-6
10+
11+
static int is_close(double a, double b)
12+
{
13+
return fabs(a - b) <= fmax(ABS_TOL, REL_TOL * fmax(fabs(a), fabs(b)));
14+
}
15+
16+
static void csr_to_dense(const CSR_Matrix *A, double *dense)
17+
{
18+
for (int row = 0; row < A->m; row++)
19+
{
20+
for (int idx = A->p[row]; idx < A->p[row + 1]; idx++)
21+
{
22+
dense[row * A->n + A->i[idx]] = A->x[idx];
23+
}
24+
}
25+
}
26+
27+
double *numerical_jacobian(expr *node, const double *u, double h)
28+
{
29+
int m = node->size;
30+
int n = node->n_vars;
31+
double inv_2h = 1.0 / (2.0 * h);
32+
33+
double *J = calloc((size_t) m * n, sizeof(double));
34+
double *u_work = malloc(n * sizeof(double));
35+
double *f_plus = malloc(m * sizeof(double));
36+
double *f_minus = malloc(m * sizeof(double));
37+
38+
memcpy(u_work, u, n * sizeof(double));
39+
40+
for (int j = 0; j < n; j++)
41+
{
42+
u_work[j] = u[j] + h;
43+
node->forward(node, u_work);
44+
memcpy(f_plus, node->value, m * sizeof(double));
45+
46+
u_work[j] = u[j] - h;
47+
node->forward(node, u_work);
48+
memcpy(f_minus, node->value, m * sizeof(double));
49+
50+
u_work[j] = u[j];
51+
52+
for (int k = 0; k < m; k++)
53+
{
54+
J[k * n + j] = (f_plus[k] - f_minus[k]) * inv_2h;
55+
}
56+
}
57+
58+
free(f_minus);
59+
free(f_plus);
60+
free(u_work);
61+
return J;
62+
}
63+
64+
int check_jacobian(expr *node, const double *u, double h)
65+
{
66+
int m = node->size;
67+
int n = node->n_vars;
68+
69+
node->jacobian_init(node);
70+
node->forward(node, u);
71+
node->eval_jacobian(node);
72+
73+
double *J_num = numerical_jacobian(node, u, h);
74+
75+
/* restore expression state after perturbations */
76+
node->forward(node, u);
77+
78+
double *J_analytical = calloc((size_t) m * n, sizeof(double));
79+
csr_to_dense(node->jacobian, J_analytical);
80+
81+
int result = 1;
82+
for (int i = 0; i < m * n; i++)
83+
{
84+
if (!is_close(J_analytical[i], J_num[i]))
85+
{
86+
int row = i / n;
87+
int col = i % n;
88+
printf(" check_jacobian FAILED at (%d, %d):"
89+
" analytical=%e, numerical=%e\n",
90+
row, col, J_analytical[i], J_num[i]);
91+
result = 0;
92+
break;
93+
}
94+
}
95+
96+
free(J_analytical);
97+
free(J_num);
98+
return result;
99+
}

tests/numerical_diff.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef NUMERICAL_DIFF_H
2+
#define NUMERICAL_DIFF_H
3+
4+
#include "expr.h"
5+
6+
#define NUMERICAL_DIFF_DEFAULT_H 1e-7
7+
8+
/* Compute dense numerical Jacobian via central differences.
9+
* Returns malloc'd row-major array (node->size x node->n_vars).
10+
* Caller must free(). */
11+
double *numerical_jacobian(expr *node, const double *u, double h);
12+
13+
/* Evaluate analytical Jacobian, compute numerical Jacobian,
14+
* and compare. Returns 1 on match, 0 on mismatch.
15+
* Prints diagnostic on first failing entry. */
16+
int check_jacobian(expr *node, const double *u, double h);
17+
18+
#endif /* NUMERICAL_DIFF_H */
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include <string.h>
2+
3+
#include "affine.h"
4+
#include "elementwise_univariate.h"
5+
#include "minunit.h"
6+
#include "numerical_diff.h"
7+
8+
const char *test_check_jacobian_composite_log(void)
9+
{
10+
double u_vals[6] = {0, 0, 1, 2, 3, 0};
11+
12+
CSR_Matrix *A = new_csr_matrix(2, 6, 6);
13+
double Ax[6] = {3, 2, 1, 2, 1, 1};
14+
int Ai[6] = {2, 3, 4, 2, 3, 4};
15+
int Ap[3] = {0, 3, 6};
16+
memcpy(A->x, Ax, 6 * sizeof(double));
17+
memcpy(A->i, Ai, 6 * sizeof(int));
18+
memcpy(A->p, Ap, 3 * sizeof(int));
19+
20+
expr *u = new_variable(3, 1, 2, 6);
21+
expr *Au = new_linear(u, A, NULL);
22+
expr *log_node = new_log(Au);
23+
24+
mu_assert("check_jacobian failed",
25+
check_jacobian(log_node, u_vals, NUMERICAL_DIFF_DEFAULT_H));
26+
27+
free_expr(log_node);
28+
free_csr_matrix(A);
29+
return 0;
30+
}

0 commit comments

Comments
 (0)