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