|
15 | 15 | * See the License for the specific language governing permissions and |
16 | 16 | * limitations under the License. |
17 | 17 | */ |
| 18 | +#include "affine.h" |
18 | 19 | #include "bivariate.h" |
19 | 20 | #include "subexpr.h" |
| 21 | +#include "utils/CSR_Matrix.h" |
20 | 22 | #include "utils/linalg_sparse_matmuls.h" |
21 | 23 | #include <stdlib.h> |
22 | 24 |
|
23 | 25 | /* This file implements the atom 'right_matmul' corresponding to the operation y = |
24 | 26 | f(x) @ A, where A is a given matrix and f(x) is an arbitrary expression. |
25 | | - Here, f(x) can be a vector-valued expression and a matrix-valued |
26 | | - expression. The dimensions are f(x) - p x n, A - n x q, y - p x q. |
27 | | - Note that here A does not have global column indices but it is a local matrix. |
28 | | - This is an important distinction compared to linear_op_expr. |
29 | | -
|
30 | | - * To compute the forward pass: vec(y) = B @ vec(f(x)), |
31 | | - where B = A^T kron I_p is a Kronecker product of size (p*q) x (p*n). |
32 | | -
|
33 | | - * To compute the Jacobian: J_y = B @ J_f(x), where J_f(x) is the |
34 | | - Jacobian of f(x) of size (p*n) x n_vars. |
35 | | -
|
36 | | - * To compute the contribution to the Lagrange Hessian: we form |
37 | | - w_child = B^T @ w and then evaluate the hessian of f(x). |
38 | | -*/ |
39 | | - |
40 | | -static void forward(expr *node, const double *u) |
41 | | -{ |
42 | | - expr *x = node->left; |
43 | | - |
44 | | - /* child's forward pass */ |
45 | | - node->left->forward(node->left, u); |
46 | | - |
47 | | - /* y = x * A, vec(y) = B @ vec(x) */ |
48 | | - csr_matvec_wo_offset(((right_matmul_expr *) node)->B, x->value, node->value); |
49 | | -} |
50 | | - |
51 | | -static bool is_affine(const expr *node) |
52 | | -{ |
53 | | - return node->left->is_affine(node->left); |
54 | | -} |
55 | | - |
56 | | -static void free_type_data(expr *node) |
57 | | -{ |
58 | | - right_matmul_expr *right_node = (right_matmul_expr *) node; |
59 | | - free_csr_matrix(right_node->B); |
60 | | - free_csr_matrix(right_node->BT); |
61 | | - if (right_node->CSC_work) |
62 | | - { |
63 | | - free_csc_matrix(right_node->CSC_work); |
64 | | - } |
65 | | - right_node->B = NULL; |
66 | | - right_node->BT = NULL; |
67 | | - right_node->CSC_work = NULL; |
68 | | -} |
69 | | - |
70 | | -static void jacobian_init(expr *node) |
71 | | -{ |
72 | | - expr *x = node->left; |
73 | | - right_matmul_expr *right_node = (right_matmul_expr *) node; |
74 | | - |
75 | | - /* initialize child's jacobian and precompute sparsity of its transpose */ |
76 | | - x->jacobian_init(x); |
77 | | - right_node->CSC_work = csr_to_csc_fill_sparsity(x->jacobian, node->iwork); |
78 | | - |
79 | | - /* precompute sparsity of this node's jacobian */ |
80 | | - node->jacobian = csr_csc_matmul_alloc(right_node->B, right_node->CSC_work); |
81 | | -} |
82 | | - |
83 | | -static void eval_jacobian(expr *node) |
84 | | -{ |
85 | | - expr *x = node->left; |
86 | | - right_matmul_expr *right_node = (right_matmul_expr *) node; |
87 | | - |
88 | | - /* evaluate child's jacobian and convert to CSC*/ |
89 | | - x->eval_jacobian(x); |
90 | | - csr_to_csc_fill_values(x->jacobian, right_node->CSC_work, node->iwork); |
91 | | - |
92 | | - /* compute this node's jacobian */ |
93 | | - csr_csc_matmul_fill_values(right_node->B, right_node->CSC_work, node->jacobian); |
94 | | -} |
95 | | - |
96 | | -static void wsum_hess_init(expr *node) |
97 | | -{ |
98 | | - /* initialize child's hessian */ |
99 | | - expr *x = node->left; |
100 | | - x->wsum_hess_init(x); |
101 | | - |
102 | | - /* allocate this node's hessian with the same sparsity as child's */ |
103 | | - node->wsum_hess = new_csr_matrix(node->n_vars, node->n_vars, x->wsum_hess->nnz); |
104 | | - memcpy(node->wsum_hess->p, x->wsum_hess->p, (node->n_vars + 1) * sizeof(int)); |
105 | | - memcpy(node->wsum_hess->i, x->wsum_hess->i, x->wsum_hess->nnz * sizeof(int)); |
106 | | - |
107 | | - /* Allocate workspace for B^T @ w */ |
108 | | - node->dwork = (double *) malloc(x->d1 * x->d2 * sizeof(double)); |
109 | | -} |
110 | | - |
111 | | -static void eval_wsum_hess(expr *node, const double *w) |
112 | | -{ |
113 | | - /* Compute B^T @ w, where B = A^T ⊗ I_p */ |
114 | | - right_matmul_expr *right_node = (right_matmul_expr *) node; |
115 | | - |
116 | | - /* B^T @ w computes the weights for the child expression */ |
117 | | - csr_matvec_wo_offset(right_node->BT, w, node->dwork); |
118 | | - |
119 | | - /* Propagate to child */ |
120 | | - node->left->eval_wsum_hess(node->left, node->dwork); |
121 | | - memcpy(node->wsum_hess->x, node->left->wsum_hess->x, |
122 | | - node->wsum_hess->nnz * sizeof(double)); |
123 | | -} |
124 | | - |
| 27 | + We implement this by expressing right matmul in terms of left matmul and |
| 28 | + transpose: f(x) @ A = (A^T @ f(x)^T)^T. */ |
125 | 29 | expr *new_right_matmul(expr *u, const CSR_Matrix *A) |
126 | 30 | { |
127 | | - /* Allocate the type-specific struct */ |
128 | | - right_matmul_expr *right_matmul_node = |
129 | | - (right_matmul_expr *) calloc(1, sizeof(right_matmul_expr)); |
130 | | - expr *node = &right_matmul_node->base; |
131 | | - |
132 | | - /* Output dimensions: u is p x n, A is n x q, output is p x q */ |
133 | | - int p = u->d1; |
134 | | - int q = A->n; |
| 31 | + /* We can express right matmul using left matmul and transpose: |
| 32 | + u @ A = (A^T @ u^T)^T. */ |
| 33 | + int *work_transpose = (int *) malloc(A->n * sizeof(int)); |
| 34 | + CSR_Matrix *AT = transpose(A, work_transpose); |
135 | 35 |
|
136 | | - init_expr(node, p, q, u->n_vars, forward, jacobian_init, eval_jacobian, |
137 | | - is_affine, wsum_hess_init, eval_wsum_hess, free_type_data); |
138 | | - node->left = u; |
139 | | - expr_retain(u); |
| 36 | + expr *u_transpose = new_transpose(u); |
| 37 | + expr *left_matmul = new_left_matmul(u_transpose, AT); |
| 38 | + expr *node = new_transpose(left_matmul); |
140 | 39 |
|
141 | | - /* create B = A^T kron I_p and its transpose */ |
142 | | - node->iwork = (int *) malloc(node->n_vars * sizeof(int)); |
143 | | - CSR_Matrix *AT = transpose(A, node->iwork); |
144 | | - right_matmul_node->B = kron_identity_csr(AT, p); |
145 | | - right_matmul_node->BT = kron_identity_csr(A, p); |
146 | 40 | free_csr_matrix(AT); |
147 | | - |
148 | | - right_matmul_node->CSC_work = NULL; |
149 | | - |
| 41 | + free(work_transpose); |
150 | 42 | return node; |
151 | 43 | } |
0 commit comments