Skip to content

Commit 8fa2826

Browse files
committed
more jacobians
1 parent 05b64ef commit 8fa2826

28 files changed

Lines changed: 1608 additions & 16 deletions

include/bivariate.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef BIVARIATE_H
2+
#define BIVARIATE_H
3+
4+
#include "expr.h"
5+
6+
expr *new_elementwise_mult(expr *left, expr *right);
7+
expr *new_rel_entr_vector_args(expr *left, expr *right);
8+
expr *new_quad_over_lin(expr *left, expr *right);
9+
10+
expr *new_rel_entr_first_arg_scalar(expr *left, expr *right);
11+
expr *new_rel_entr_second_arg_scalar(expr *left, expr *right);
12+
13+
#endif /* BIVARIATE_H */

include/elementwise_univariate.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ expr *new_entr(expr *child);
99
expr *new_sin(expr *child);
1010
expr *new_cos(expr *child);
1111
expr *new_tan(expr *child);
12+
expr *new_sinh(expr *child);
13+
expr *new_tanh(expr *child);
14+
expr *new_asinh(expr *child);
15+
expr *new_atanh(expr *child);
16+
expr *new_logistic(expr *child);
17+
expr *new_power(expr *child, int p);
1218

1319
/* the jacobian for elementwise atoms are always initialized in the
1420
same way and implement the chain rule in the same way */
@@ -19,4 +25,4 @@ void eval_jacobian_elementwise(expr *node);
1925
so we can have a common implementation */
2026
bool is_affine_elementwise(expr *node);
2127

22-
#endif /* ELEMENTWISE_H */
28+
#endif /* ELEMENTWISE_UNIVARIATE_H */

include/expr.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ typedef struct expr
2626
int m;
2727
int n_vars;
2828
int var_id;
29+
int refcount;
2930
struct expr *left;
3031
struct expr *right;
3132
double *dwork;
33+
int *iwork;
34+
int p; /* power of power expression */
3235

3336
// ------------------------------------------------------------------------
3437
// forward pass related quantities
@@ -40,6 +43,8 @@ typedef struct expr
4043
// jacobian related quantities
4144
// ------------------------------------------------------------------------
4245
CSR_Matrix *jacobian;
46+
CSR_Matrix *Q;
47+
CSR_Matrix *CSR_work;
4348
jacobian_init_fn jacobian_init;
4449
eval_jacobian_fn eval_jacobian;
4550
eval_local_jacobian_fn eval_local_jacobian;
@@ -50,4 +55,7 @@ typedef struct expr
5055
expr *new_expr(int m, int n_vars);
5156
void free_expr(expr *node);
5257

58+
/* Reference counting helpers */
59+
void expr_retain(expr *node);
60+
5361
#endif /* EXPR_H */

include/other.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#ifndef OTHER_H
2+
#define OTHER_H
3+
4+
#include "expr.h"
5+
#include "utils/CSR_Matrix.h"
6+
7+
expr *new_quad_form(expr *child, CSR_Matrix *Q);
8+
9+
#endif /* OTHER_H */

include/utils/CSR_Matrix.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#ifndef CSR_MATRIX_H
22
#define CSR_MATRIX_H
3+
#include <stdbool.h>
34

45
/* CSR (Compressed Sparse Row) Matrix Format
56
*
@@ -30,9 +31,20 @@ void free_csr_matrix(CSR_Matrix *matrix);
3031
/* Copy CSR matrix A to C */
3132
void copy_csr_matrix(const CSR_Matrix *A, CSR_Matrix *C);
3233

33-
/* matvec y = Ax, where A indices minus col_offset gives x indices */
34+
/* matvec y = Ax, where A indices minus col_offset gives x indices. Returns y as
35+
* dense. */
3436
void csr_matvec(const CSR_Matrix *A, const double *x, double *y, int col_offset);
3537

38+
/* C = z^T A is assumed to have one row. C must have column indices pre-computed
39+
and transposed matrix AT must be provided. Fills in values of C only.
40+
*/
41+
void csr_matvec_fill_values(const CSR_Matrix *AT, const double *z, CSR_Matrix *C);
42+
43+
/* Insert value into CSR matrix A with just one row at col_idx. Assumes that A
44+
has enough space and that A does not have an element at col_idx. It does update
45+
nnz. */
46+
void csr_insert_value(CSR_Matrix *A, int col_idx, double value);
47+
3648
/* Compute C = diag(d) * A where d is an array and A, C are CSR matrices
3749
* d must have length m
3850
* C must be pre-allocated with same dimensions as A */
@@ -43,4 +55,15 @@ void diag_csr_mult(const double *d, const CSR_Matrix *A, CSR_Matrix *C);
4355
* C must be pre-allocated with sufficient nnz capacity */
4456
void sum_csr_matrices(const CSR_Matrix *A, const CSR_Matrix *B, CSR_Matrix *C);
4557

58+
/* Compute C = diag(d1) * A + diag(d2) * B where A, B, C are CSR matrices */
59+
void sum_scaled_csr_matrices(const CSR_Matrix *A, const CSR_Matrix *B, CSR_Matrix *C,
60+
const double *d1, const double *d2);
61+
62+
/* Count number of columns with nonzero entries */
63+
int count_nonzero_cols(const CSR_Matrix *A, bool *col_nz);
64+
65+
/* inserts 'idx' into array 'arr' in sorted order, and moves the other elements */
66+
void insert_idx(int idx, int *arr, int len);
67+
68+
CSR_Matrix *transpose(const CSR_Matrix *A, int *iwork);
4669
#endif /* CSR_MATRIX_H */

src/affine/add.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ expr *new_add(expr *left, expr *right)
4949

5050
node->left = left;
5151
node->right = right;
52+
expr_retain(left);
53+
expr_retain(right);
5254
node->forward = add_forward;
5355
node->is_affine = is_affine;
5456
node->jacobian_init = jacobian_init;

src/affine/linear_op.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ expr *new_linear(expr *u, const CSR_Matrix *A)
2222
if (!node) return NULL;
2323

2424
node->left = u;
25+
expr_retain(u);
2526
node->forward = forward;
2627
node->is_affine = is_affine;
2728

src/affine/variable.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ static void forward(expr *node, const double *u)
77
memcpy(node->value, u + node->var_id, node->m * sizeof(double));
88
}
99

10+
static void jacobian_init(expr *node)
11+
{
12+
node->jacobian = new_csr_matrix(node->m, node->n_vars, node->m);
13+
for (int j = 0; j < node->m; j++)
14+
{
15+
node->jacobian->p[j] = j;
16+
node->jacobian->i[j] = j + node->var_id;
17+
node->jacobian->x[j] = 1.0;
18+
}
19+
node->jacobian->p[node->m] = node->m;
20+
}
21+
1022
static bool is_affine(expr *node)
1123
{
1224
(void) node;
@@ -21,6 +33,8 @@ expr *new_variable(int m, int var_id, int n_vars)
2133
node->forward = forward;
2234
node->var_id = var_id;
2335
node->is_affine = is_affine;
36+
node->jacobian_init = jacobian_init;
37+
// node->jacobian = NULL;
2438

2539
return node;
2640
}

src/bivariate/multiply.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#include "bivariate.h"
2+
#include <math.h>
3+
#include <stdlib.h>
4+
5+
// ------------------------------------------------------------------------------
6+
// Implementation of elementwise multiplication when both arguments are vectors.
7+
// If one argument is a scalar variable, the broadcasting should be represented
8+
// as a linear operator child node?
9+
// ------------------------------------------------------------------------------
10+
static void forward(expr *node, const double *u)
11+
{
12+
expr *x = node->left;
13+
expr *y = node->right;
14+
15+
/* children's forward passes */
16+
x->forward(x, u);
17+
y->forward(y, u);
18+
19+
/* local forward pass */
20+
for (int i = 0; i < node->m; i++)
21+
{
22+
node->value[i] = x->value[i] * y->value[i];
23+
}
24+
}
25+
26+
static void jacobian_init(expr *node)
27+
{
28+
/* if a child is a variable we initialize its jacobian for a
29+
short chain rule implementation */
30+
if (node->left->var_id != -1)
31+
{
32+
node->left->jacobian_init(node->left);
33+
}
34+
35+
if (node->right->var_id != -1)
36+
{
37+
node->right->jacobian_init(node->right);
38+
}
39+
40+
node->dwork = (double *) malloc(2 * node->m * sizeof(double));
41+
int nnz_max = node->left->jacobian->nnz + node->right->jacobian->nnz;
42+
node->jacobian = new_csr_matrix(node->m, node->n_vars, nnz_max);
43+
}
44+
45+
static void eval_jacobian(expr *node)
46+
{
47+
expr *x = node->left;
48+
expr *y = node->right;
49+
50+
/* chain rule */
51+
sum_scaled_csr_matrices(x->jacobian, y->jacobian, node->jacobian, y->value,
52+
x->value);
53+
}
54+
55+
expr *new_elementwise_mult(expr *left, expr *right)
56+
{
57+
expr *node = new_expr(left->m, left->n_vars);
58+
node->left = left;
59+
node->right = right;
60+
expr_retain(left);
61+
expr_retain(right);
62+
node->forward = forward;
63+
node->jacobian_init = jacobian_init;
64+
node->eval_jacobian = eval_jacobian;
65+
return node;
66+
}

src/bivariate/quad_over_lin.c

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#include "bivariate.h"
2+
#include <assert.h>
3+
#include <math.h>
4+
#include <stdlib.h>
5+
6+
// ------------------------------------------------------------------------------
7+
// Implementation of quad-over-lin. The second argument will always be a variable
8+
// that only appears in this node and as the left-hand side of another equality
9+
// constraint.
10+
// ------------------------------------------------------------------------------
11+
static void forward(expr *node, const double *u)
12+
{
13+
expr *x = node->left;
14+
expr *y = node->right;
15+
16+
/* children's forward passes */
17+
x->forward(x, u);
18+
y->forward(y, u);
19+
20+
/* local forward pass */
21+
node->value[0] = 0.0;
22+
23+
for (int i = 0; i < x->m; i++)
24+
{
25+
node->value[0] += x->value[i] * x->value[i];
26+
}
27+
28+
node->value[0] /= y->value[0];
29+
}
30+
31+
static void jacobian_init(expr *node)
32+
{
33+
expr *x = node->left;
34+
expr *y = node->right;
35+
36+
/* if left node is a variable */
37+
if (x->var_id != -1)
38+
{
39+
node->jacobian = new_csr_matrix(1, node->n_vars, x->m + 1);
40+
node->jacobian->p[0] = 0;
41+
node->jacobian->p[1] = x->m + 1;
42+
43+
/* if x has lower idx than y*/
44+
if (x->var_id < y->var_id)
45+
{
46+
for (int j = 0; j < x->m; j++)
47+
{
48+
node->jacobian->i[j] = x->var_id + j;
49+
}
50+
node->jacobian->i[x->m] = y->var_id;
51+
}
52+
else /* y has lower idx than x */
53+
{
54+
node->jacobian->i[0] = y->var_id;
55+
for (int j = 0; j < x->m; j++)
56+
{
57+
node->jacobian->i[j + 1] = x->var_id + j;
58+
}
59+
}
60+
}
61+
else /* left node is not a variable */
62+
{
63+
node->dwork = (double *) malloc(x->m * sizeof(double));
64+
65+
/* compute required allocation and allocate jacobian */
66+
bool *col_nz = (bool *) calloc(
67+
node->n_vars, sizeof(bool)); /* TODO: could use iwork here instead*/
68+
int nonzero_cols = count_nonzero_cols(x->jacobian, col_nz);
69+
node->jacobian = new_csr_matrix(1, node->n_vars, nonzero_cols + 1);
70+
71+
/* precompute column indices */
72+
node->jacobian->nnz = 0;
73+
for (int j = 0; j < node->n_vars; j++)
74+
{
75+
if (col_nz[j])
76+
{
77+
node->jacobian->i[node->jacobian->nnz] = j;
78+
node->jacobian->nnz++;
79+
}
80+
}
81+
assert(nonzero_cols == node->jacobian->nnz);
82+
83+
free(col_nz);
84+
85+
/* insert y variable index at correct position */
86+
insert_idx(y->var_id, node->jacobian->i, node->jacobian->nnz);
87+
node->jacobian->nnz += 1;
88+
node->jacobian->p[0] = 0;
89+
node->jacobian->p[1] = node->jacobian->nnz;
90+
91+
/* store A^T of child's A to simplify chain rule computation */
92+
node->iwork = (int *) malloc(x->jacobian->n * sizeof(int));
93+
node->CSR_work = transpose(x->jacobian, node->iwork);
94+
95+
/* find position where y should be inserted */
96+
for (int j = 0; j < node->jacobian->nnz; j++)
97+
{
98+
if (node->jacobian->i[j] == y->var_id)
99+
{
100+
node->iwork[0] = j;
101+
break;
102+
}
103+
}
104+
}
105+
}
106+
107+
static void eval_jacobian(expr *node)
108+
{
109+
expr *x = node->left;
110+
expr *y = node->right;
111+
112+
/* if x is a variable */
113+
if (x->var_id != -1)
114+
{
115+
/* if x has lower idx than y*/
116+
if (x->var_id < y->var_id)
117+
{
118+
for (int j = 0; j < x->m; j++)
119+
{
120+
node->jacobian->x[j] = (2.0 * x->value[j]) / y->value[0];
121+
}
122+
node->jacobian->x[x->m] = -node->value[0] / y->value[0];
123+
}
124+
else /* y has lower idx than x */
125+
{
126+
node->jacobian->x[0] = -node->value[0] / y->value[0];
127+
for (int j = 0; j < x->m; j++)
128+
{
129+
node->jacobian->x[j + 1] = (2.0 * x->value[j]) / y->value[0];
130+
}
131+
}
132+
}
133+
else /* x is not a variable */
134+
{
135+
/* local jacobian */
136+
for (int j = 0; j < x->m; j++)
137+
{
138+
node->dwork[j] = (2.0 * x->value[j]) / y->value[0];
139+
}
140+
141+
/* chain rule (no derivative wrt y) */
142+
csr_matvec_fill_values(node->CSR_work, node->dwork, node->jacobian);
143+
144+
/* insert derivative wrt y at right place (for correctness this assumes
145+
that y does not appear in the denominator, but this will always be
146+
the case since y is a new variable for the numerator) */
147+
node->jacobian->x[node->iwork[0]] = -node->value[0] / y->value[0];
148+
}
149+
}
150+
151+
expr *new_quad_over_lin(expr *left, expr *right)
152+
{
153+
expr *node = new_expr(left->m, left->n_vars);
154+
node->left = left;
155+
node->right = right;
156+
expr_retain(left);
157+
expr_retain(right);
158+
node->forward = forward;
159+
node->jacobian_init = jacobian_init;
160+
node->eval_jacobian = eval_jacobian;
161+
return node;
162+
}

0 commit comments

Comments
 (0)