|
| 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