1 // Copyright 2017 Steven Diamond
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "cvxcore.hpp"
16 #include "LinOp.hpp"
17 #include "LinOpOperations.hpp"
18 #include "ProblemData.hpp"
19 #include "Utils.hpp"
20 #include <iostream>
21 #include <map>
22 #include <utility>
23
24 #ifdef _OPENMP
25 #include "omp.h"
26 #endif
27
28
29 /* function: add_matrix_to_vectors
30 *
31 * This function adds a matrix to our sparse matrix triplet
32 * representation, by using eigen's sparse matrix iterator
33 * This function takes horizontal and vertical offset, which indicate
34 * the offset of this block within our larger matrix.
35 */
add_matrix_to_vectors(const Matrix & block,std::vector<double> & V,std::vector<int> & I,std::vector<int> & J,int vert_offset,int horiz_offset)36 void add_matrix_to_vectors(const Matrix &block, std::vector<double> &V,
37 std::vector<int> &I, std::vector<int> &J,
38 int vert_offset, int horiz_offset) {
39 auto number_of_nonzeros = block.nonZeros();
40 V.reserve(V.size() + number_of_nonzeros);
41 I.reserve(I.size() + number_of_nonzeros);
42 J.reserve(J.size() + number_of_nonzeros);
43
44 for (int k = 0; k < block.outerSize(); ++k) {
45 for (Matrix::InnerIterator it(block, k); it; ++it) {
46 V.push_back(it.value());
47
48 /* Push back current row and column indices */
49 I.push_back(it.row() + vert_offset);
50 J.push_back(it.col() + horiz_offset);
51 }
52 }
53 }
54
process_constraint(const LinOp & lin,ProblemData & problemData,int vert_offset,int var_length,const std::map<int,int> & id_to_col)55 void process_constraint(const LinOp &lin, ProblemData &problemData,
56 int vert_offset, int var_length,
57 const std::map<int, int> &id_to_col) {
58 /* Get the coefficient for the current constraint */
59 Tensor coeffs = lin_to_tensor(lin);
60
61 // Convert variable ids into column offsets.
62 // Parameter IDs and vectors of matrices remain.
63 for (auto it = coeffs.begin(); it != coeffs.end(); ++it) {
64 int param_id = it->first;
65 const DictMat& var_map = it->second;
66 for (auto in_it = var_map.begin(); in_it != var_map.end(); ++in_it) {
67 int var_id = in_it->first; // Horiz offset determined by the id
68 const std::vector<Matrix>& blocks = in_it->second;
69 // Constant term is last column.
70 for (unsigned i = 0; i < blocks.size(); ++i) {
71 int horiz_offset;
72 if (var_id == CONSTANT_ID) { // Add to CONSTANT_VEC if linop is constant
73 horiz_offset = var_length;
74 } else {
75 horiz_offset = id_to_col.at(var_id);
76 }
77
78 #ifdef _OPENMP
79 #pragma omp critical
80 #endif
81 {
82 add_matrix_to_vectors(blocks[i], problemData.TensorV[param_id][i],
83 problemData.TensorI[param_id][i],
84 problemData.TensorJ[param_id][i], vert_offset,
85 horiz_offset);
86 }
87
88 }
89 }
90 }
91 }
92
93 /* Returns the number of rows in the matrix assuming vertical stacking
94 of coefficient matrices */
get_total_constraint_length(std::vector<LinOp * > constraints)95 int get_total_constraint_length(std::vector<LinOp *> constraints) {
96 int result = 0;
97 for (unsigned i = 0; i < constraints.size(); i++) {
98 result += vecprod(constraints[i]->get_shape());
99 }
100 return result;
101 }
102
103 /* Returns the number of rows in the matrix using the user provided vertical
104 offsets for each constraint. */
get_total_constraint_length(std::vector<LinOp * > & constraints,std::vector<int> & constr_offsets)105 int get_total_constraint_length(std::vector<LinOp *> &constraints,
106 std::vector<int> &constr_offsets) {
107 /* Must specify an offset for each constraint */
108 if (constraints.size() != constr_offsets.size()) {
109 std::cerr << "Error: Invalid constraint offsets: ";
110 std::cerr << "CONSTR_OFFSET must be the same length as CONSTRAINTS"
111 << std::endl;
112 exit(-1);
113 }
114
115 int offset_end = 0;
116 /* Offsets must be monotonically increasing */
117 for (unsigned i = 0; i < constr_offsets.size(); i++) {
118 LinOp constr = *constraints[i];
119 int offset_start = constr_offsets[i];
120 offset_end = offset_start + vecprod(constr.get_shape());
121
122 if (i + 1 < constr_offsets.size() && constr_offsets[i + 1] < offset_end) {
123 std::cerr << "Error: Invalid constraint offsets: ";
124 std::cerr << "Offsets are not monotonically increasing" << std::endl;
125 exit(-1);
126 }
127 }
128 return offset_end;
129 }
130
131 // Create a tensor with a problem data entry for each parameter,
132 // as a vector with entries equal to the parameter size.
init_data_tensor(std::map<int,int> param_to_size)133 ProblemData init_data_tensor(std::map<int, int> param_to_size) {
134 ProblemData output;
135 // Get CONSTANT_ID.
136 output.init_id(CONSTANT_ID, 1);
137 typedef std::map<int, int>::iterator it_type;
138 for (it_type it = param_to_size.begin(); it != param_to_size.end(); ++it) {
139 int param_id = it->first;
140 int param_size = it->second;
141 output.init_id(param_id, param_size);
142 }
143 return output;
144 }
145
146 /* function: build_matrix
147 *
148 * Description: Given a list of linear operations, this function returns a data
149 * structure containing a sparse matrix representation of the cone program.
150 *
151 * Input: std::vector<LinOp *> constraints, our list of constraints represented
152 * as a linear operation tree
153 *
154 * Output: prob_data, a data structure which contains a sparse representation
155 * of the coefficient matrix, a dense representation of the constant vector,
156 * and maps containing our mapping from variables, and a map from the rows of
157 * our matrix to their corresponding constraint.
158 */
build_matrix(std::vector<const LinOp * > constraints,int var_length,std::map<int,int> id_to_col,std::map<int,int> param_to_size,int num_threads)159 ProblemData build_matrix(std::vector<const LinOp *> constraints, int var_length,
160 std::map<int, int> id_to_col,
161 std::map<int, int> param_to_size, int num_threads) {
162 /* Build matrix one constraint at a time */
163 ProblemData prob_data = init_data_tensor(param_to_size);
164
165 int vert_offset = 0;
166 std::vector<std::pair<const LinOp*, int> > constraints_and_offsets;
167 constraints_and_offsets.reserve(constraints.size());
168 for (const LinOp* constraint : constraints) {
169 auto pair = std::make_pair(constraint, vert_offset);
170 constraints_and_offsets.push_back(pair);
171 vert_offset += vecprod(constraint->get_shape());
172 }
173
174 // TODO: to get full parallelism, each thread should use its own ProblemData;
175 // the ProblemData objects could be reduced afterwards (specifically
176 // the V, I, and J arrays would be merged)
177 #ifdef _OPENMP
178 if (num_threads > 0) {
179 omp_set_num_threads(num_threads);
180 }
181 #pragma omp parallel for
182 #endif
183 for (size_t i = 0; i < constraints_and_offsets.size(); ++i) {
184 const std::pair<const LinOp*, int>& pair = constraints_and_offsets.at(i);
185 const LinOp* constraint = pair.first;
186 int vert_offset = pair.second;
187 process_constraint(
188 *constraint, prob_data, vert_offset, var_length, id_to_col);
189 }
190 return prob_data;
191 }
192
193 /* See comment above for build_matrix. Requires specification of a vertical
194 offset, VERT_OFFSET, for each constraint in the vector
195 CONSTR_OFFSETS.
196
197 Valid CONSTR_OFFSETS assume that a vertical offset is provided
198 for each constraint and that the offsets are not overlapping. In particular,
199 the vertical offset for constraint i + the size of constraint i
200 must be less than the vertical offset for constraint i+1.
201 */
build_matrix(std::vector<const LinOp * > constraints,int var_length,std::map<int,int> id_to_col,std::map<int,int> param_to_size,std::vector<int> constr_offsets)202 ProblemData build_matrix(std::vector<const LinOp *> constraints, int var_length,
203 std::map<int, int> id_to_col,
204 std::map<int, int> param_to_size,
205 std::vector<int> constr_offsets) {
206 ProblemData prob_data = init_data_tensor(param_to_size);
207
208 /* Build matrix one constraint at a time */
209 for (unsigned i = 0; i < constraints.size(); i++) {
210 LinOp constr = *constraints[i];
211 int vert_offset = constr_offsets[i];
212 process_constraint(constr, prob_data, vert_offset, var_length, id_to_col);
213 }
214 return prob_data;
215 }
216