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