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"
43 constexpr
double kInf = std::numeric_limits<double>::infinity();
45 absl::Status IsSupported(
const MPModelProto&
model) {
47 if (validity_string.length() > 0) {
48 return absl::InvalidArgumentError(validity_string);
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");
58 if (
model.solution_hint().var_index_size() > 0) {
59 return absl::InvalidArgumentError(
"Solution Hint not supported");
61 return absl::OkStatus();
64 bool AnyVarNamed(
const MPModelProto&
model) {
65 for (
const MPVariableProto&
var :
model.variable()) {
66 if (
var.name().length() > 0) {
73 bool AnyConstraintNamed(
const MPModelProto&
model) {
74 for (
const MPConstraintProto& constraint :
model.constraint()) {
75 if (constraint.name().length() > 0) {
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]});
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);
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());
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) {
122 qp_terms_in_order.push_back(
123 {{first_index, second_index}, in_coefficients[k]});
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;
131 out_expression.add_row_ids(indices.first);
132 out_expression.add_column_ids(indices.second);
133 out_expression.add_coefficients(coeff);
137 return out_expression;
140 QuadraticConstraintProto QuadraticConstraintFromMPModelToMathOpt(
142 QuadraticConstraintProto out_constraint;
145 out_constraint.set_name(std::string(
name));
146 LinearTermsFromMPModelToMathOpt(
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(),
154 return out_constraint;
157 SosConstraintProto SosConstraintFromMPModelToMathOpt(
159 SosConstraintProto out_constraint;
160 out_constraint.set_name(std::string(
name));
162 LinearExpressionProto& expr = *out_constraint.add_expressions();
164 expr.add_coefficients(1.0);
167 out_constraint.add_weights(
weight);
169 return out_constraint;
173 absl::StatusOr<IndicatorConstraintProto>
174 IndicatorConstraintFromMPModelToMathOpt(
176 IndicatorConstraintProto out_constraint;
177 out_constraint.set_name(std::string(
name));
179 out_constraint.set_activate_on_zero(
in_constraint.has_var_value() &&
181 out_constraint.set_lower_bound(
in_constraint.constraint().lower_bound());
182 out_constraint.set_upper_bound(
in_constraint.constraint().upper_bound());
183 LinearTermsFromMPModelToMathOpt(
186 *out_constraint.mutable_expression()->mutable_ids(),
187 *out_constraint.mutable_expression()->mutable_values());
188 return out_constraint;
191 absl::StatusOr<MPGeneralConstraintProto> SosConstraintFromMathOptToMPModel(
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;
197 MPSosConstraint& out_constraint =
198 *out_general_constraint.mutable_sos_constraint();
199 out_constraint.set_type(sos_type);
201 out_constraint.add_weight(
weight);
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");
210 out_constraint.add_var_index(
211 variable_id_to_mp_position.at(expression.ids(0)));
213 return out_general_constraint;
218 absl::StatusOr<::operations_research::math_opt::ModelProto>
223 output.set_name(
model.name());
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);
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;
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());
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);
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());
270 objective->set_maximize(
model.maximize());
271 objective->set_offset(
model.objective_offset());
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);
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());
291 num_non_zeros += constraint.var_index_size();
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);
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);
309 terms_in_order.emplace_back(constraint.var_index(k),
coefficient);
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);
317 terms_in_order.clear();
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);
331 case MPGeneralConstraintProto::kSosConstraint: {
333 general_constraint.sos_constraint();
335 case operations_research::MPSosConstraint::SOS1_DEFAULT: {
337 .mutable_sos1_constraints())[output.sos1_constraints_size()] =
341 case operations_research::MPSosConstraint::SOS2: {
343 .mutable_sos2_constraints())[output.sos2_constraints_size()] =
350 case MPGeneralConstraintProto::kIndicatorConstraint: {
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));
363 return absl::InternalError(
364 "Reached unrecognized general constraint in MPModelProto");
372 const ::operations_research::math_opt::ModelProto&
model) {
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;
383 output.set_name(
model.name());
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));
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),
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));
411 output.set_maximize(
model.objective().maximize());
412 output.set_objective_offset(
model.objective().offset());
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);
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));
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);
448 MPGeneralConstraintProto& out_general_constraint =
449 *output.add_general_constraint();
451 MPQuadraticConstraint& out_constraint =
452 *out_general_constraint.mutable_quadratic_constraint();
456 out_constraint.add_var_index(variable_id_to_mp_position[
index]);
457 out_constraint.add_coefficient(coeff);
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(
463 out_constraint.add_qvar2_index(
464 variable_id_to_mp_position[
in_constraint.quadratic_terms().column_ids(
466 out_constraint.add_qcoefficient(
472 SosConstraintFromMathOptToMPModel(
474 variable_id_to_mp_position));
479 *output.add_general_constraint(),
480 SosConstraintFromMathOptToMPModel(
in_constraint, MPSosConstraint::SOS2,
481 variable_id_to_mp_position));
488 MPGeneralConstraintProto& out_general_constraint =
489 *output.add_general_constraint();
491 MPIndicatorConstraint& out_constraint =
492 *out_general_constraint.mutable_indicator_constraint();
493 out_constraint.set_var_index(
495 out_constraint.set_var_value(
in_constraint.activate_on_zero() ? 0 : 1);
496 out_constraint.mutable_constraint()->set_lower_bound(
498 out_constraint.mutable_constraint()->set_upper_bound(
501 out_constraint.mutable_constraint()->add_var_index(
502 variable_id_to_mp_position[
index]);
503 out_constraint.mutable_constraint()->add_coefficient(coeff);
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
int NumVariables(const VariablesProto &variables)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
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.