Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
proto_converter.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <limits>
20#include <optional>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/algorithm/container.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/log/check.h"
28#include "absl/status/status.h"
29#include "absl/status/statusor.h"
30#include "absl/strings/string_view.h"
31#include "absl/types/span.h"
33#include "ortools/linear_solver/linear_solver.pb.h"
37#include "ortools/math_opt/model.pb.h"
38#include "ortools/math_opt/model_parameters.pb.h"
39#include "ortools/math_opt/sparse_containers.pb.h"
41
42namespace operations_research {
43namespace math_opt {
44namespace {
45
46absl::Status IsSupported(const MPModelProto& model) {
47 std::string validity_string = FindErrorInMPModelProto(model);
48 if (!validity_string.empty()) {
49 return absl::InvalidArgumentError(validity_string);
50 }
51 for (const MPGeneralConstraintProto& general_constraint :
52 model.general_constraint()) {
53 if (!(general_constraint.has_quadratic_constraint() ||
54 general_constraint.has_sos_constraint() ||
55 general_constraint.has_indicator_constraint())) {
56 return absl::InvalidArgumentError("Unsupported general constraint");
57 }
58 }
59 return absl::OkStatus();
60}
61
62bool AnyVarNamed(const MPModelProto& model) {
63 for (const MPVariableProto& var : model.variable()) {
64 if (var.name().length() > 0) {
65 return true;
66 }
67 }
68 return false;
69}
70
71bool AnyConstraintNamed(const MPModelProto& model) {
72 for (const MPConstraintProto& constraint : model.constraint()) {
73 if (constraint.name().length() > 0) {
74 return true;
75 }
76 }
77 return false;
78}
79
80void LinearTermsFromMPModelToMathOpt(
81 const absl::Span<const int32_t> in_ids,
82 const absl::Span<const double> in_coeffs,
83 google::protobuf::RepeatedField<int64_t>& out_ids,
84 google::protobuf::RepeatedField<double>& out_coeffs) {
85 CHECK_EQ(in_ids.size(), in_coeffs.size());
86 const int num_terms = static_cast<int>(in_ids.size());
87 std::vector<std::pair<int, double>> linear_terms_in_order;
88 for (int i = 0; i < num_terms; ++i) {
89 linear_terms_in_order.push_back({in_ids[i], in_coeffs[i]});
90 }
91 absl::c_sort(linear_terms_in_order);
92 out_ids.Resize(num_terms, -1);
93 out_coeffs.Resize(num_terms, std::numeric_limits<double>::quiet_NaN());
94 for (int i = 0; i < num_terms; ++i) {
95 out_ids.Set(i, linear_terms_in_order[i].first);
96 out_coeffs.Set(i, linear_terms_in_order[i].second);
97 }
98}
99
100// Copies quadratic terms from MPModelProto format to MathOpt format. In
101// particular, MathOpt requires three things not enforced by MPModelProto:
102// 1. No duplicate entries,
103// 2. No lower triangular entries, and
104// 3. Lexicographic sortedness of (row_id, column_id) keys.
105SparseDoubleMatrixProto QuadraticTermsFromMPModelToMathOpt(
106 const absl::Span<const int32_t> in_row_var_indices,
107 const absl::Span<const int32_t> in_col_var_indices,
108 const absl::Span<const double> in_coefficients) {
109 CHECK_EQ(in_row_var_indices.size(), in_col_var_indices.size());
110 CHECK_EQ(in_row_var_indices.size(), in_coefficients.size());
111
112 SparseDoubleMatrixProto out_expression;
113 std::vector<std::pair<std::pair<int32_t, int32_t>, double>> qp_terms_in_order;
114 for (int k = 0; k < in_row_var_indices.size(); ++k) {
115 int32_t first_index = in_row_var_indices[k];
116 int32_t second_index = in_col_var_indices[k];
117 if (first_index > second_index) {
118 std::swap(first_index, second_index);
119 }
120 qp_terms_in_order.push_back(
121 {{first_index, second_index}, in_coefficients[k]});
122 }
123 absl::c_sort(qp_terms_in_order);
124 std::pair<int32_t, int32_t> previous = {-1, -1};
125 for (const auto& [indices, coeff] : qp_terms_in_order) {
126 if (indices == previous) {
127 *out_expression.mutable_coefficients()->rbegin() += coeff;
128 } else {
129 out_expression.add_row_ids(indices.first);
130 out_expression.add_column_ids(indices.second);
131 out_expression.add_coefficients(coeff);
132 previous = indices;
133 }
134 }
135 return out_expression;
136}
137
138QuadraticConstraintProto QuadraticConstraintFromMPModelToMathOpt(
139 const MPQuadraticConstraint& in_constraint, const absl::string_view name) {
140 QuadraticConstraintProto out_constraint;
141 out_constraint.set_lower_bound(in_constraint.lower_bound());
142 out_constraint.set_upper_bound(in_constraint.upper_bound());
143 out_constraint.set_name(std::string(name));
144 LinearTermsFromMPModelToMathOpt(
145 in_constraint.var_index(), in_constraint.coefficient(),
146 *out_constraint.mutable_linear_terms()->mutable_ids(),
147 *out_constraint.mutable_linear_terms()->mutable_values());
148 *out_constraint.mutable_quadratic_terms() =
149 QuadraticTermsFromMPModelToMathOpt(in_constraint.qvar1_index(),
150 in_constraint.qvar2_index(),
151 in_constraint.qcoefficient());
152 return out_constraint;
153}
154
155SosConstraintProto SosConstraintFromMPModelToMathOpt(
156 const MPSosConstraint& in_constraint, const absl::string_view name) {
157 SosConstraintProto out_constraint;
158 out_constraint.set_name(std::string(name));
159 for (const int j : in_constraint.var_index()) {
160 LinearExpressionProto& expr = *out_constraint.add_expressions();
161 expr.add_ids(j);
162 expr.add_coefficients(1.0);
163 }
164 for (const double weight : in_constraint.weight()) {
165 out_constraint.add_weights(weight);
166 }
167 return out_constraint;
168}
169
170// NOTE: We ignore the `is_lazy` field in the MPIndicatorConstraint.
171absl::StatusOr<IndicatorConstraintProto>
172IndicatorConstraintFromMPModelToMathOpt(
173 const MPIndicatorConstraint& in_constraint, const absl::string_view name) {
174 IndicatorConstraintProto out_constraint;
175 out_constraint.set_name(std::string(name));
176 out_constraint.set_indicator_id(in_constraint.var_index());
177 out_constraint.set_activate_on_zero(in_constraint.has_var_value() &&
178 in_constraint.var_value() == 0);
179 out_constraint.set_lower_bound(in_constraint.constraint().lower_bound());
180 out_constraint.set_upper_bound(in_constraint.constraint().upper_bound());
181 LinearTermsFromMPModelToMathOpt(
182 in_constraint.constraint().var_index(),
183 in_constraint.constraint().coefficient(),
184 *out_constraint.mutable_expression()->mutable_ids(),
185 *out_constraint.mutable_expression()->mutable_values());
186 return out_constraint;
187}
188
189absl::StatusOr<MPGeneralConstraintProto> SosConstraintFromMathOptToMPModel(
190 const SosConstraintProto& in_constraint,
191 const MPSosConstraint::Type sos_type,
192 const absl::flat_hash_map<int64_t, int>& variable_id_to_mp_position) {
193 MPGeneralConstraintProto out_general_constraint;
194 out_general_constraint.set_name(in_constraint.name());
195 MPSosConstraint& out_constraint =
196 *out_general_constraint.mutable_sos_constraint();
197 out_constraint.set_type(sos_type);
198 for (const double weight : in_constraint.weights()) {
199 out_constraint.add_weight(weight);
200 }
201 for (const LinearExpressionProto& expression : in_constraint.expressions()) {
202 if (expression.ids_size() != 1 || expression.coefficients(0) != 1.0 ||
203 expression.offset() != 0.0) {
204 return absl::InvalidArgumentError(
205 "MPModelProto does not support SOS constraints with "
206 "expressions that are not equivalent to a single variable");
207 }
208 out_constraint.add_var_index(
209 variable_id_to_mp_position.at(expression.ids(0)));
210 }
211 return out_general_constraint;
212}
213
214} // namespace
215
216absl::StatusOr<::operations_research::math_opt::ModelProto>
217MPModelProtoToMathOptModel(const ::operations_research::MPModelProto& model) {
218 RETURN_IF_ERROR(IsSupported(model));
219
220 ModelProto output;
221 output.set_name(model.name());
222
223 math_opt::VariablesProto* const vars = output.mutable_variables();
224 int linear_objective_non_zeros = 0;
225 const int num_vars = model.variable_size();
226 const bool vars_have_name = AnyVarNamed(model);
227 vars->mutable_lower_bounds()->Reserve(num_vars);
228 vars->mutable_upper_bounds()->Reserve(num_vars);
229 vars->mutable_integers()->Reserve(num_vars);
230 if (vars_have_name) {
231 vars->mutable_names()->Reserve(num_vars);
232 }
233 for (int i = 0; i < model.variable_size(); ++i) {
234 const MPVariableProto& var = model.variable(i);
235 if (var.objective_coefficient() != 0.0) {
236 ++linear_objective_non_zeros;
237 }
238 vars->add_ids(i);
239 vars->add_lower_bounds(var.lower_bound());
240 vars->add_upper_bounds(var.upper_bound());
241 vars->add_integers(var.is_integer());
242 if (vars_have_name) {
243 vars->add_names(var.name());
244 }
245 }
246
247 math_opt::ObjectiveProto* const objective = output.mutable_objective();
248 if (linear_objective_non_zeros > 0) {
249 objective->mutable_linear_coefficients()->mutable_ids()->Reserve(
250 linear_objective_non_zeros);
251 objective->mutable_linear_coefficients()->mutable_values()->Reserve(
252 linear_objective_non_zeros);
253 for (int j = 0; j < num_vars; ++j) {
254 const double value = model.variable(j).objective_coefficient();
255 if (value == 0.0) continue;
256 objective->mutable_linear_coefficients()->add_ids(j);
257 objective->mutable_linear_coefficients()->add_values(value);
258 }
259 }
260 const MPQuadraticObjective& origin_qp_terms = model.quadratic_objective();
261 const int num_qp_terms = origin_qp_terms.coefficient().size();
262 if (num_qp_terms > 0) {
263 *objective->mutable_quadratic_coefficients() =
264 QuadraticTermsFromMPModelToMathOpt(origin_qp_terms.qvar1_index(),
265 origin_qp_terms.qvar2_index(),
266 origin_qp_terms.coefficient());
267 }
268 objective->set_maximize(model.maximize());
269 objective->set_offset(model.objective_offset());
270
271 math_opt::LinearConstraintsProto* const constraints =
272 output.mutable_linear_constraints();
273 const int num_constraints = model.constraint_size();
274 const bool constraints_have_name = AnyConstraintNamed(model);
275 int num_non_zeros = 0;
276 constraints->mutable_lower_bounds()->Reserve(num_constraints);
277 constraints->mutable_upper_bounds()->Reserve(num_constraints);
278 if (constraints_have_name) {
279 constraints->mutable_names()->Reserve(num_constraints);
280 }
281 for (int i = 0; i < num_constraints; ++i) {
282 const MPConstraintProto& constraint = model.constraint(i);
283 constraints->add_ids(i);
284 constraints->add_lower_bounds(constraint.lower_bound());
285 constraints->add_upper_bounds(constraint.upper_bound());
286 if (constraints_have_name) {
287 constraints->add_names(constraint.name());
288 }
289 num_non_zeros += constraint.var_index_size();
290 }
291
292 SparseDoubleMatrixProto* const matrix =
293 output.mutable_linear_constraint_matrix();
294 matrix->mutable_column_ids()->Reserve(num_non_zeros);
295 matrix->mutable_row_ids()->Reserve(num_non_zeros);
296 matrix->mutable_coefficients()->Reserve(num_non_zeros);
297 // This allocation is reused across loop iterations, use caution!
298 std::vector<std::pair<int, double>> terms_in_order;
299 for (int i = 0; i < num_constraints; ++i) {
300 const MPConstraintProto& constraint = model.constraint(i);
301 const int constraint_non_zeros = constraint.var_index_size();
302 for (int k = 0; k < constraint_non_zeros; ++k) {
303 const double coefficient = constraint.coefficient(k);
304 if (coefficient == 0.0) {
305 continue;
306 }
307 terms_in_order.emplace_back(constraint.var_index(k), coefficient);
308 }
309 std::sort(terms_in_order.begin(), terms_in_order.end());
310 for (const auto& term : terms_in_order) {
311 matrix->add_row_ids(i);
312 matrix->add_column_ids(term.first);
313 matrix->add_coefficients(term.second);
314 }
315 terms_in_order.clear();
316 }
317
318 for (const MPGeneralConstraintProto& general_constraint :
319 model.general_constraint()) {
320 const std::string& in_name = general_constraint.name();
321 switch (general_constraint.general_constraint_case()) {
322 case MPGeneralConstraintProto::kQuadraticConstraint: {
323 (*output.mutable_quadratic_constraints())
324 [output.quadratic_constraints_size()] =
325 QuadraticConstraintFromMPModelToMathOpt(
326 general_constraint.quadratic_constraint(), in_name);
327 break;
328 }
329 case MPGeneralConstraintProto::kSosConstraint: {
330 const MPSosConstraint& in_constraint =
331 general_constraint.sos_constraint();
332 switch (in_constraint.type()) {
333 case operations_research::MPSosConstraint::SOS1_DEFAULT: {
334 (*output
335 .mutable_sos1_constraints())[output.sos1_constraints_size()] =
336 SosConstraintFromMPModelToMathOpt(in_constraint, in_name);
337 break;
338 }
339 case operations_research::MPSosConstraint::SOS2: {
340 (*output
341 .mutable_sos2_constraints())[output.sos2_constraints_size()] =
342 SosConstraintFromMPModelToMathOpt(in_constraint, in_name);
343 break;
344 }
345 }
346 break;
347 }
348 case MPGeneralConstraintProto::kIndicatorConstraint: {
349 // Note that the open-source version of ASSIGN_OR_RETURN does not
350 // support inlining the new_indicator_constraint expression.
351 auto& new_indicator_constraint =
352 (*output.mutable_indicator_constraints())
353 [output.indicator_constraints_size()];
355 new_indicator_constraint,
356 IndicatorConstraintFromMPModelToMathOpt(
357 general_constraint.indicator_constraint(), in_name));
358 break;
359 }
360 default: {
361 return absl::InternalError(
362 "Reached unrecognized general constraint in MPModelProto");
363 }
364 }
365 }
366 return output;
367}
368
369absl::StatusOr<std::optional<SolutionHintProto>>
371 std::string validity_string = FindErrorInMPModelProto(model);
372 if (!validity_string.empty()) {
373 return absl::InvalidArgumentError(validity_string);
374 }
375
376 if (model.solution_hint().var_index_size() == 0) {
377 return std::nullopt;
378 }
379
380 SolutionHintProto hint;
381 auto& variable_values = *hint.mutable_variable_values();
382 LinearTermsFromMPModelToMathOpt(
383 model.solution_hint().var_index(), model.solution_hint().var_value(),
384 *variable_values.mutable_ids(), *variable_values.mutable_values());
385
386 return hint;
387}
388
389absl::StatusOr<::operations_research::MPModelProto> MathOptModelToMPModelProto(
390 const ::operations_research::math_opt::ModelProto& model) {
392 if (!model.second_order_cone_constraints().empty()) {
393 return absl::InvalidArgumentError(
394 "translating models with second-order cone constraints is not "
395 "supported");
396 }
397
398 const bool vars_have_name = model.variables().names_size() > 0;
399 const bool constraints_have_name =
400 model.linear_constraints().names_size() > 0;
401 absl::flat_hash_map<int64_t, int> variable_id_to_mp_position;
402 absl::flat_hash_map<int64_t, MPConstraintProto*>
403 constraint_id_to_mp_constraint;
404
405 MPModelProto output;
406 output.set_name(model.name());
407
408 const int num_vars = NumVariables(model.variables());
409 output.mutable_variable()->Reserve(num_vars);
410 for (int j = 0; j < num_vars; ++j) {
411 MPVariableProto* const variable = output.add_variable();
412 variable_id_to_mp_position.emplace(model.variables().ids(j), j);
413 variable->set_lower_bound(model.variables().lower_bounds(j));
414 variable->set_upper_bound(model.variables().upper_bounds(j));
415 variable->set_is_integer(model.variables().integers(j));
416 if (vars_have_name) {
417 variable->set_name(model.variables().names(j));
418 }
419 }
420
421 const int num_constraints = NumConstraints(model.linear_constraints());
422 output.mutable_constraint()->Reserve(num_constraints);
423 for (int i = 0; i < num_constraints; ++i) {
424 MPConstraintProto* const constraint = output.add_constraint();
425 constraint_id_to_mp_constraint.emplace(model.linear_constraints().ids(i),
426 constraint);
427 constraint->set_lower_bound(model.linear_constraints().lower_bounds(i));
428 constraint->set_upper_bound(model.linear_constraints().upper_bounds(i));
429 if (constraints_have_name) {
430 constraint->set_name(model.linear_constraints().names(i));
431 }
432 }
433
434 output.set_maximize(model.objective().maximize());
435 output.set_objective_offset(model.objective().offset());
436 for (const auto& [var, coef] :
437 MakeView(model.objective().linear_coefficients())) {
438 const int var_position = variable_id_to_mp_position[var];
439 MPVariableProto* const variable = output.mutable_variable(var_position);
440 variable->set_objective_coefficient(coef);
441 }
442 const SparseDoubleMatrixProto& origin_qp_terms =
443 model.objective().quadratic_coefficients();
444 if (!origin_qp_terms.coefficients().empty()) {
445 MPQuadraticObjective& destination_qp_terms =
446 *output.mutable_quadratic_objective();
447 for (int k = 0; k < origin_qp_terms.coefficients().size(); ++k) {
448 destination_qp_terms.add_qvar1_index(
449 variable_id_to_mp_position[origin_qp_terms.row_ids(k)]);
450 destination_qp_terms.add_qvar2_index(
451 variable_id_to_mp_position[origin_qp_terms.column_ids(k)]);
452 destination_qp_terms.add_coefficient(origin_qp_terms.coefficients(k));
453 }
454 }
455
456 // TODO(user): use the constraint iterator from scip_solver.cc here.
457 const int constraint_non_zeros =
458 model.linear_constraint_matrix().coefficients_size();
459 for (int k = 0; k < constraint_non_zeros; ++k) {
460 const int64_t constraint_id = model.linear_constraint_matrix().row_ids(k);
461 MPConstraintProto* const constraint =
462 constraint_id_to_mp_constraint[constraint_id];
463 const int64_t variable_id = model.linear_constraint_matrix().column_ids(k);
464 const int variable_position = variable_id_to_mp_position[variable_id];
465 constraint->add_var_index(variable_position);
466 const double value = model.linear_constraint_matrix().coefficients(k);
467 constraint->add_coefficient(value);
468 }
469
470 for (const auto& [id, in_constraint] : model.quadratic_constraints()) {
471 MPGeneralConstraintProto& out_general_constraint =
472 *output.add_general_constraint();
473 out_general_constraint.set_name(in_constraint.name());
474 MPQuadraticConstraint& out_constraint =
475 *out_general_constraint.mutable_quadratic_constraint();
476 out_constraint.set_lower_bound(in_constraint.lower_bound());
477 out_constraint.set_upper_bound(in_constraint.upper_bound());
478 for (const auto [index, coeff] : MakeView(in_constraint.linear_terms())) {
479 out_constraint.add_var_index(variable_id_to_mp_position[index]);
480 out_constraint.add_coefficient(coeff);
481 }
482 for (int k = 0; k < in_constraint.quadratic_terms().row_ids_size(); ++k) {
483 out_constraint.add_qvar1_index(
484 variable_id_to_mp_position[in_constraint.quadratic_terms().row_ids(
485 k)]);
486 out_constraint.add_qvar2_index(
487 variable_id_to_mp_position[in_constraint.quadratic_terms().column_ids(
488 k)]);
489 out_constraint.add_qcoefficient(
490 in_constraint.quadratic_terms().coefficients(k));
491 }
492 }
493 for (const auto& [id, in_constraint] : model.sos1_constraints()) {
494 ASSIGN_OR_RETURN(*output.add_general_constraint(),
495 SosConstraintFromMathOptToMPModel(
496 in_constraint, MPSosConstraint::SOS1_DEFAULT,
497 variable_id_to_mp_position));
498 }
499
500 for (const auto& [id, in_constraint] : model.sos2_constraints()) {
502 *output.add_general_constraint(),
503 SosConstraintFromMathOptToMPModel(in_constraint, MPSosConstraint::SOS2,
504 variable_id_to_mp_position));
505 }
506
507 for (const auto& [id, in_constraint] : model.indicator_constraints()) {
508 if (!in_constraint.has_indicator_id()) {
509 continue;
510 }
511 MPGeneralConstraintProto& out_general_constraint =
512 *output.add_general_constraint();
513 out_general_constraint.set_name(in_constraint.name());
514 MPIndicatorConstraint& out_constraint =
515 *out_general_constraint.mutable_indicator_constraint();
516 out_constraint.set_var_index(
517 variable_id_to_mp_position[in_constraint.indicator_id()]);
518 out_constraint.set_var_value(in_constraint.activate_on_zero() ? 0 : 1);
519 out_constraint.mutable_constraint()->set_lower_bound(
520 in_constraint.lower_bound());
521 out_constraint.mutable_constraint()->set_upper_bound(
522 in_constraint.upper_bound());
523 for (const auto [index, coeff] : MakeView(in_constraint.expression())) {
524 out_constraint.mutable_constraint()->add_var_index(
525 variable_id_to_mp_position[index]);
526 out_constraint.mutable_constraint()->add_coefficient(coeff);
527 }
528 }
529
530 return output;
531}
532
533} // namespace math_opt
534} // namespace operations_research
IntegerValue size
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
const std::string name
A name for logging purposes.
int64_t value
IntVar * var
int64_t coef
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
int index
std::optional< ModelSolveParameters::SolutionHint > hint
absl::StatusOr< std::optional< SolutionHintProto > > MPModelProtoSolutionHintToMathOptHint(const MPModelProto &model)
int NumVariables(const VariablesProto &variables)
absl::StatusOr<::operations_research::MPModelProto > MathOptModelToMPModelProto(const ::operations_research::math_opt::ModelProto &model)
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::StatusOr< ModelSummary > ValidateModel(const ModelProto &model, const bool check_names)
In SWIG mode, we don't want anything besides these top-level includes.
std::string FindErrorInMPModelProto(const MPModelProto &model, double abs_value_threshold, const bool accept_trivially_infeasible_bounds)
int64_t weight
Definition pack.cc:510
int64_t coefficient
bool in_constraint
Definition trace.cc:435