OR-Tools  9.3
proto_converter.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <iterator>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/container/flat_hash_map.h"
24#include "absl/status/status.h"
25#include "absl/status/statusor.h"
27#include "ortools/linear_solver/linear_solver.pb.h"
31#include "ortools/math_opt/model.pb.h"
32#include "ortools/math_opt/sparse_containers.pb.h"
34
35namespace operations_research {
36namespace math_opt {
37namespace {
38
39absl::Status IsSupported(const MPModelProto& model) {
40 std::string validity_string = FindErrorInMPModelProto(model);
41 if (validity_string.length() > 0) {
42 return absl::InvalidArgumentError(validity_string);
43 }
44 if (model.general_constraint_size() > 0) {
45 return absl::InvalidArgumentError("General constraints are not supported");
46 }
47 if (model.solution_hint().var_index_size() > 0) {
48 return absl::InvalidArgumentError("Solution Hint not supported");
49 }
50 return absl::OkStatus();
51}
52
53absl::Status IsSupported(const math_opt::ModelProto& model) {
54 return ValidateModel(model);
55}
56
57bool AnyVarNamed(const MPModelProto& model) {
58 for (const MPVariableProto& var : model.variable()) {
59 if (var.name().length() > 0) {
60 return true;
61 }
62 }
63 return false;
64}
65
66bool AnyConstraintNamed(const MPModelProto& model) {
67 for (const MPConstraintProto& constraint : model.constraint()) {
68 if (constraint.name().length() > 0) {
69 return true;
70 }
71 }
72 return false;
73}
74
75} // namespace
76
77absl::StatusOr<::operations_research::math_opt::ModelProto>
78MPModelProtoToMathOptModel(const ::operations_research::MPModelProto& model) {
79 RETURN_IF_ERROR(IsSupported(model));
80
81 ModelProto output;
82 output.set_name(model.name());
83
84 math_opt::VariablesProto* const vars = output.mutable_variables();
85 int linear_objective_non_zeros = 0;
86 const int num_vars = model.variable_size();
87 const bool vars_have_name = AnyVarNamed(model);
88 vars->mutable_lower_bounds()->Reserve(num_vars);
89 vars->mutable_upper_bounds()->Reserve(num_vars);
90 vars->mutable_integers()->Reserve(num_vars);
91 if (vars_have_name) {
92 vars->mutable_names()->Reserve(num_vars);
93 }
94 for (int i = 0; i < model.variable_size(); ++i) {
95 const MPVariableProto& var = model.variable(i);
96 if (var.objective_coefficient() != 0.0) {
97 ++linear_objective_non_zeros;
98 }
99 vars->add_ids(i);
100 vars->add_lower_bounds(var.lower_bound());
101 vars->add_upper_bounds(var.upper_bound());
102 vars->add_integers(var.is_integer());
103 if (vars_have_name) {
104 vars->add_names(var.name());
105 }
106 }
107
108 math_opt::ObjectiveProto* const objective = output.mutable_objective();
109 if (linear_objective_non_zeros > 0) {
110 objective->mutable_linear_coefficients()->mutable_ids()->Reserve(
111 linear_objective_non_zeros);
112 objective->mutable_linear_coefficients()->mutable_values()->Reserve(
113 linear_objective_non_zeros);
114 for (int j = 0; j < num_vars; ++j) {
115 const double value = model.variable(j).objective_coefficient();
116 if (value == 0.0) continue;
117 objective->mutable_linear_coefficients()->add_ids(j);
118 objective->mutable_linear_coefficients()->add_values(value);
119 }
120 }
121 const MPQuadraticObjective& origin_qp_terms = model.quadratic_objective();
122 const int num_qp_terms = origin_qp_terms.coefficient().size();
123 if (num_qp_terms > 0) {
124 // ObjectiveProto requires three things that may not be satisfied by
125 // MPQuadraticObjective:
126 // 1. No duplicate entries
127 // 2. No lower triangular entries
128 // 3. Lexicographic sortedness of (row_id, column_id) keys
129 std::vector<std::pair<std::pair<int, int>, double>> qp_terms_in_order;
130 for (int k = 0; k < num_qp_terms; ++k) {
131 int first_index = origin_qp_terms.qvar1_index(k);
132 int second_index = origin_qp_terms.qvar2_index(k);
133 if (first_index > second_index) {
134 std::swap(first_index, second_index);
135 }
136 qp_terms_in_order.emplace_back(std::make_pair(first_index, second_index),
137 origin_qp_terms.coefficient(k));
138 }
139 std::sort(qp_terms_in_order.begin(), qp_terms_in_order.end());
140 SparseDoubleMatrixProto& destination_qp_terms =
141 *objective->mutable_quadratic_coefficients();
142 std::pair<int, int> previous = {-1, -1};
143 for (const auto& [indices, coeff] : qp_terms_in_order) {
144 if (indices == previous) {
145 *destination_qp_terms.mutable_coefficients()->rbegin() += coeff;
146 } else {
147 destination_qp_terms.add_row_ids(indices.first);
148 destination_qp_terms.add_column_ids(indices.second);
149 destination_qp_terms.add_coefficients(coeff);
150 previous = indices;
151 }
152 }
153 }
154 objective->set_maximize(model.maximize());
155 objective->set_offset(model.objective_offset());
156
157 math_opt::LinearConstraintsProto* const constraints =
158 output.mutable_linear_constraints();
159 const int num_constraints = model.constraint_size();
160 const bool constraints_have_name = AnyConstraintNamed(model);
161 int num_non_zeros = 0;
162 constraints->mutable_lower_bounds()->Reserve(num_constraints);
163 constraints->mutable_upper_bounds()->Reserve(num_constraints);
164 if (constraints_have_name) {
165 constraints->mutable_names()->Reserve(num_constraints);
166 }
167 for (int i = 0; i < num_constraints; ++i) {
168 const MPConstraintProto& constraint = model.constraint(i);
169 constraints->add_ids(i);
170 constraints->add_lower_bounds(constraint.lower_bound());
171 constraints->add_upper_bounds(constraint.upper_bound());
172 if (constraints_have_name) {
173 constraints->add_names(constraint.name());
174 }
175 num_non_zeros += constraint.var_index_size();
176 }
177
178 SparseDoubleMatrixProto* const matrix =
179 output.mutable_linear_constraint_matrix();
180 matrix->mutable_column_ids()->Reserve(num_non_zeros);
181 matrix->mutable_row_ids()->Reserve(num_non_zeros);
182 matrix->mutable_coefficients()->Reserve(num_non_zeros);
183 // This allocation is reused across loop iterations, use caution!
184 std::vector<std::pair<int, double>> terms_in_order;
185 for (int i = 0; i < num_constraints; ++i) {
186 const MPConstraintProto& constraint = model.constraint(i);
187 const int constraint_non_zeros = constraint.var_index_size();
188 for (int k = 0; k < constraint_non_zeros; ++k) {
189 const double coefficient = constraint.coefficient(k);
190 if (coefficient == 0.0) {
191 continue;
192 }
193 terms_in_order.emplace_back(constraint.var_index(k), coefficient);
194 }
195 std::sort(terms_in_order.begin(), terms_in_order.end());
196 for (const auto& term : terms_in_order) {
197 matrix->add_row_ids(i);
198 matrix->add_column_ids(term.first);
199 matrix->add_coefficients(term.second);
200 }
201 terms_in_order.clear();
202 }
203 return output;
204}
205
206absl::StatusOr<::operations_research::MPModelProto> MathOptModelToMPModelProto(
207 const ::operations_research::math_opt::ModelProto& model) {
208 RETURN_IF_ERROR(IsSupported(model));
209
210 const bool vars_have_name = model.variables().names_size() > 0;
211 const bool constraints_have_name =
212 model.linear_constraints().names_size() > 0;
213 absl::flat_hash_map<int64_t, int> variable_id_to_mp_position;
214 absl::flat_hash_map<int64_t, MPConstraintProto*>
215 constraint_id_to_mp_constraint;
216
217 MPModelProto output;
218 output.set_name(model.name());
219
220 const int num_vars = NumVariables(model.variables());
221 output.mutable_variable()->Reserve(num_vars);
222 for (int j = 0; j < num_vars; ++j) {
223 MPVariableProto* const variable = output.add_variable();
224 variable_id_to_mp_position.emplace(model.variables().ids(j), j);
225 variable->set_lower_bound(model.variables().lower_bounds(j));
226 variable->set_upper_bound(model.variables().upper_bounds(j));
227 variable->set_is_integer(model.variables().integers(j));
228 if (vars_have_name) {
229 variable->set_name(model.variables().names(j));
230 }
231 }
232
233 const int num_constraints = NumConstraints(model.linear_constraints());
234 output.mutable_constraint()->Reserve(num_constraints);
235 for (int i = 0; i < num_constraints; ++i) {
236 MPConstraintProto* const constraint = output.add_constraint();
237 constraint_id_to_mp_constraint.emplace(model.linear_constraints().ids(i),
238 constraint);
239 constraint->set_lower_bound(model.linear_constraints().lower_bounds(i));
240 constraint->set_upper_bound(model.linear_constraints().upper_bounds(i));
241 if (constraints_have_name) {
242 constraint->set_name(model.linear_constraints().names(i));
243 }
244 }
245
246 output.set_maximize(model.objective().maximize());
247 output.set_objective_offset(model.objective().offset());
248 for (const auto& [var, coef] :
249 MakeView(model.objective().linear_coefficients())) {
250 const int var_position = variable_id_to_mp_position[var];
251 MPVariableProto* const variable = output.mutable_variable(var_position);
252 variable->set_objective_coefficient(coef);
253 }
254 const SparseDoubleMatrixProto& origin_qp_terms =
255 model.objective().quadratic_coefficients();
256 if (!origin_qp_terms.coefficients().empty()) {
257 MPQuadraticObjective& destination_qp_terms =
258 *output.mutable_quadratic_objective();
259 for (int k = 0; k < origin_qp_terms.coefficients().size(); ++k) {
260 destination_qp_terms.add_qvar1_index(
261 variable_id_to_mp_position[origin_qp_terms.row_ids(k)]);
262 destination_qp_terms.add_qvar2_index(
263 variable_id_to_mp_position[origin_qp_terms.column_ids(k)]);
264 destination_qp_terms.add_coefficient(origin_qp_terms.coefficients(k));
265 }
266 }
267
268 // TODO(user): use the constraint iterator from scip_solver.cc here.
269 const int constraint_non_zeros =
270 model.linear_constraint_matrix().coefficients_size();
271 for (int k = 0; k < constraint_non_zeros; ++k) {
272 const int64_t constraint_id = model.linear_constraint_matrix().row_ids(k);
273 MPConstraintProto* const constraint =
274 constraint_id_to_mp_constraint[constraint_id];
275 const int64_t variable_id = model.linear_constraint_matrix().column_ids(k);
276 const int variable_position = variable_id_to_mp_position[variable_id];
277 constraint->add_var_index(variable_position);
278 const double value = model.linear_constraint_matrix().coefficients(k);
279 constraint->add_coefficient(value);
280 }
281
282 return output;
283}
284
285} // namespace math_opt
286} // namespace operations_research
#define RETURN_IF_ERROR(expr)
int64_t value
IntVar * var
Definition: expr_array.cc:1874
int64_t coef
Definition: expr_array.cc:1875
GRBmodel * model
int NumVariables(const VariablesProto &variables)
absl::StatusOr<::operations_research::MPModelProto > MathOptModelToMPModelProto(const ::operations_research::math_opt::ModelProto &model)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:262
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
int NumConstraints(const LinearConstraintsProto &linear_constraints)
absl::StatusOr<::operations_research::math_opt::ModelProto > MPModelProtoToMathOptModel(const ::operations_research::MPModelProto &model)
absl::Status ValidateModel(const ModelProto &model, const bool check_names)
Collection of objects used to extend the Constraint Solver library.
std::string FindErrorInMPModelProto(const MPModelProto &model, double abs_value_threshold, const bool accept_trivially_infeasible_bounds)
Returns an empty string iff the model is valid and not trivially infeasible.
int64_t coefficient
const double coeff