24 #include "absl/container/flat_hash_map.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/status/status.h"
27 #include "absl/status/statusor.h"
28 #include "absl/strings/match.h"
29 #include "absl/strings/str_cat.h"
30 #include "absl/strings/str_format.h"
31 #include "absl/types/optional.h"
35 #include "ortools/linear_solver/linear_solver.pb.h"
42 double, model_validator_infinity, 1e100,
43 "Anything above or equal to this magnitude will be considered infinity.");
48 bool IsNanOrAbsGreaterThanOrEqual(
double value,
double abs_value_threshold) {
49 return std::isnan(
value) || std::abs(
value) >= abs_value_threshold;
54 template <
typename BoundedElement>
55 std::string FindErrorInBounds(
const BoundedElement& element,
56 double abs_value_threshold,
57 const bool accept_trivially_infeasible_bounds) {
58 if (std::isnan(element.lower_bound()) || std::isnan(element.upper_bound()) ||
59 element.lower_bound() >= abs_value_threshold ||
60 element.upper_bound() <= -abs_value_threshold ||
61 (!accept_trivially_infeasible_bounds &&
62 element.lower_bound() > element.upper_bound())) {
63 return absl::StrFormat(
"Infeasible bounds: [%f, %f]", element.lower_bound(),
64 element.upper_bound());
70 std::string FindErrorInMPVariable(
71 const MPVariableProto& variable,
double abs_value_threshold,
72 const bool accept_trivially_infeasible_bounds) {
73 const std::string bound_error = FindErrorInBounds(
74 variable, abs_value_threshold, accept_trivially_infeasible_bounds);
75 if (!bound_error.empty())
return bound_error;
77 if (!accept_trivially_infeasible_bounds && variable.is_integer() &&
78 ceil(variable.lower_bound()) > floor(variable.upper_bound())) {
80 "Infeasible bounds for integer variable: [", (variable.lower_bound()),
81 ", ", (variable.upper_bound()),
"]",
" translate to the empty set");
83 if (IsNanOrAbsGreaterThanOrEqual(variable.objective_coefficient(),
84 abs_value_threshold)) {
85 return absl::StrCat(
"Invalid objective_coefficient: ",
86 (variable.objective_coefficient()));
92 template <
typename Iterable>
93 std::string FindDuplicateVarIndex(
const Iterable&
var_indices,
94 std::vector<bool>* var_mask) {
95 int duplicate_var_index = -1;
97 if ((*var_mask)[var_index]) duplicate_var_index = var_index;
98 (*var_mask)[var_index] =
true;
102 (*var_mask)[var_index] =
false;
104 if (duplicate_var_index >= 0) {
105 return absl::StrCat(
"var_index #", duplicate_var_index,
106 " appears several times");
114 std::string FindErrorInMPConstraint(
115 const MPConstraintProto& constraint, std::vector<bool>* var_mask,
116 double abs_value_threshold,
const bool accept_trivially_infeasible_bounds) {
117 const std::string bound_error = FindErrorInBounds(
118 constraint, abs_value_threshold, accept_trivially_infeasible_bounds);
119 if (!bound_error.empty())
return bound_error;
124 const int num_vars_in_model = var_mask->size();
125 const int num_vars_in_ct = constraint.var_index_size();
126 const int num_coeffs_in_ct = constraint.coefficient_size();
127 if (num_vars_in_ct != num_coeffs_in_ct) {
128 return absl::StrCat(
"var_index_size() != coefficient_size() (",
129 num_vars_in_ct,
" VS ", num_coeffs_in_ct);
131 for (
int i = 0; i < num_vars_in_ct; ++i) {
132 const int var_index = constraint.var_index(i);
133 if (var_index >= num_vars_in_model || var_index < 0) {
134 return absl::StrCat(
"var_index(", i,
")=", var_index,
135 " is out of bounds");
137 const double coeff = constraint.coefficient(i);
138 if (IsNanOrAbsGreaterThanOrEqual(coeff, abs_value_threshold)) {
139 return absl::StrCat(
"coefficient(", i,
")=", (coeff),
" is invalid");
143 const std::string error =
144 FindDuplicateVarIndex(constraint.var_index(), var_mask);
145 if (!error.empty())
return error;
148 return std::string();
151 std::string CroppedConstraintDebugString(
const MPConstraintProto& constraint) {
152 const int kMaxPrintedVars = 10;
154 MPConstraintProto constraint_light = constraint;
155 std::string suffix_str;
156 if (constraint.var_index_size() > kMaxPrintedVars) {
157 constraint_light.mutable_var_index()->Truncate(kMaxPrintedVars);
158 absl::StrAppend(&suffix_str,
159 " (var_index cropped; size=", constraint.var_index_size(),
162 if (constraint.coefficient_size() > kMaxPrintedVars) {
163 constraint_light.mutable_coefficient()->Truncate(kMaxPrintedVars);
164 absl::StrAppend(&suffix_str,
" (coefficient cropped; size=",
165 constraint.coefficient_size(),
").");
167 return absl::StrCat(
"Constraint proto: ",
171 bool IsBoolean(
const MPVariableProto& variable) {
172 if (variable.lower_bound() < 0)
return false;
173 if (variable.upper_bound() > 1)
return false;
174 return variable.is_integer();
177 std::string FindErrorInMPIndicatorConstraint(
178 const MPModelProto&
model,
const MPIndicatorConstraint& indicator,
179 std::vector<bool>* var_mask,
double abs_value_threshold,
180 bool accept_trivially_infeasible_bounds) {
181 if (!indicator.has_var_index()) {
182 return "var_index is required.";
184 const int var_index = indicator.var_index();
185 if (var_index < 0 || var_index >=
model.variable_size()) {
186 return absl::StrCat(
"var_index=", var_index,
" is out of bounds.");
188 if (!IsBoolean(
model.variable(var_index))) {
189 return absl::StrCat(
"var_index=", var_index,
" is not Boolean.");
191 const int var_value = indicator.var_value();
192 if (var_value < 0 || var_value > 1) {
193 return absl::StrCat(
"var_value=", var_value,
" must be 0 or 1.");
195 const MPConstraintProto& constraint = indicator.constraint();
197 FindErrorInMPConstraint(constraint, var_mask, abs_value_threshold,
198 accept_trivially_infeasible_bounds);
199 if (!error.empty()) {
202 return absl::StrCat(error,
" in constraint ",
203 CroppedConstraintDebugString(constraint));
208 std::string FindErrorInMPSosConstraint(
const MPModelProto&
model,
209 const MPSosConstraint& sos,
210 std::vector<bool>* var_mask,
211 double abs_value_threshold) {
212 if (sos.weight_size() != 0 && sos.weight_size() != sos.var_index_size()) {
213 return "weight_size() > 0 and var_index_size() != weight_size()";
215 for (
const int var_index : sos.var_index()) {
216 if (var_index < 0 || var_index >=
model.variable_size()) {
217 return absl::StrCat(
"var_index=", var_index,
" is out of bounds.");
220 for (
int i = 0; i < sos.weight_size(); ++i) {
221 if (IsNanOrAbsGreaterThanOrEqual(sos.weight(i), abs_value_threshold)) {
222 return absl::StrCat(
"Invalid weight: ", sos.weight(i));
224 if (i == 0)
continue;
225 if (sos.weight(i - 1) >= sos.weight(i)) {
226 return "SOS weights must be strictly increasing";
230 const std::string error = FindDuplicateVarIndex(sos.var_index(), var_mask);
231 if (!error.empty())
return error;
236 std::string FindErrorInMPQuadraticConstraint(
237 const MPModelProto&
model,
const MPQuadraticConstraint& qcst,
238 std::vector<bool>* var_mask,
double abs_value_threshold,
239 bool accept_trivially_infeasible_bounds) {
240 const int num_vars =
model.variable_size();
242 if (qcst.var_index_size() != qcst.coefficient_size()) {
243 return "var_index_size() != coefficient_size()";
246 const std::string bound_error = FindErrorInBounds(
247 qcst, abs_value_threshold, accept_trivially_infeasible_bounds);
248 if (!bound_error.empty())
return bound_error;
250 for (
int i = 0; i < qcst.var_index_size(); ++i) {
251 if (qcst.var_index(i) < 0 || qcst.var_index(i) >= num_vars) {
252 return absl::StrCat(
"var_index(", i,
")=", qcst.var_index(i),
253 " is invalid.",
" It must be in [0, ", num_vars,
")");
256 if (IsNanOrAbsGreaterThanOrEqual(qcst.coefficient(i),
257 abs_value_threshold)) {
258 return absl::StrCat(
"coefficient(", i,
")=", qcst.coefficient(i),
262 const std::string duplicate_error =
263 FindDuplicateVarIndex(qcst.var_index(), var_mask);
264 if (!duplicate_error.empty())
return duplicate_error;
266 if (qcst.qvar1_index_size() != qcst.qvar2_index_size() ||
267 qcst.qvar1_index_size() != qcst.qcoefficient_size()) {
268 return "quadratic indices and coefficients must have the same size";
270 for (
int i = 0; i < qcst.qvar1_index_size(); ++i) {
271 if (qcst.qvar1_index(i) >= num_vars || qcst.qvar1_index(i) < 0) {
272 return absl::StrCat(
"qvar1_index(", i,
")=", qcst.qvar1_index(i),
273 " is invalid.",
" It must be in [0, ", num_vars,
")");
276 if (qcst.qvar2_index(i) >= num_vars || qcst.qvar2_index(i) < 0) {
277 return absl::StrCat(
"qvar2_index(", i,
")=", qcst.qvar2_index(i),
278 " is invalid.",
" It must be in [0, ", num_vars,
")");
281 if (IsNanOrAbsGreaterThanOrEqual(qcst.qcoefficient(i),
282 abs_value_threshold)) {
283 return absl::StrCat(
"qcoefficient(", i,
")=", qcst.qcoefficient(i),
291 std::string FindErrorInMPAbsConstraint(
const MPModelProto&
model,
292 const MPAbsConstraint& abs) {
293 if (!abs.has_var_index()) {
294 return "var_index is required.";
296 if (!abs.has_resultant_var_index()) {
297 return "resultant_var_index is required.";
300 const int num_vars =
model.variable_size();
301 if (abs.var_index() < 0 || abs.var_index() >= num_vars) {
302 return absl::StrCat(
"var_index=", abs.var_index(),
" is invalid.",
303 " It must be in [0, ", num_vars,
")");
305 if (abs.resultant_var_index() < 0 || abs.resultant_var_index() >= num_vars) {
306 return absl::StrCat(
"var_index=", abs.resultant_var_index(),
" is invalid.",
307 " It must be in [0, ", num_vars,
")");
312 std::string FindErrorInMPAndOrConstraint(
const MPModelProto&
model,
313 const MPArrayConstraint& and_or) {
314 if (and_or.var_index_size() == 0) {
315 return "var_index cannot be empty.";
317 if (!and_or.has_resultant_var_index()) {
318 return "resultant_var_index is required.";
321 const int num_vars =
model.variable_size();
322 for (
int i = 0; i < and_or.var_index_size(); ++i) {
323 if (and_or.var_index(i) < 0 || and_or.var_index(i) >= num_vars) {
324 return absl::StrCat(
"var_index(", i,
")=", and_or.var_index(i),
325 " is invalid.",
" It must be in [0, ", num_vars,
")");
327 if (!IsBoolean(
model.variable(and_or.var_index(i)))) {
328 return absl::StrCat(
"var_index=", i,
" is not Boolean.");
331 if (and_or.resultant_var_index() < 0 ||
332 and_or.resultant_var_index() >= num_vars) {
333 return absl::StrCat(
"resultant_var_index=", and_or.resultant_var_index(),
334 " is invalid.",
" It must be in [0, ", num_vars,
")");
336 if (!IsBoolean(
model.variable(and_or.resultant_var_index()))) {
337 return absl::StrCat(
"resultant_var_index is not Boolean.");
342 std::string FindErrorInMPMinMaxConstraint(
343 const MPModelProto&
model,
const MPArrayWithConstantConstraint& min_max,
344 double abs_value_threshold) {
345 if (min_max.var_index_size() == 0) {
346 return "var_index cannot be empty.";
348 if (!min_max.has_resultant_var_index()) {
349 return "resultant_var_index is required.";
352 if (IsNanOrAbsGreaterThanOrEqual(min_max.constant(), abs_value_threshold)) {
353 return absl::StrCat(
"Invalid constant: ", (min_max.constant()));
356 const int num_vars =
model.variable_size();
357 for (
int i = 0; i < min_max.var_index_size(); ++i) {
358 if (min_max.var_index(i) < 0 || min_max.var_index(i) >= num_vars) {
359 return absl::StrCat(
"var_index(", i,
")=", min_max.var_index(i),
360 " is invalid.",
" It must be in [0, ", num_vars,
")");
363 if (min_max.resultant_var_index() < 0 ||
364 min_max.resultant_var_index() >= num_vars) {
365 return absl::StrCat(
"resultant_var_index=", min_max.resultant_var_index(),
366 " is invalid.",
" It must be in [0, ", num_vars,
")");
371 std::string FindErrorInQuadraticObjective(
const MPQuadraticObjective& qobj,
373 double abs_value_threshold) {
374 if (qobj.qvar1_index_size() != qobj.qvar2_index_size() ||
375 qobj.qvar1_index_size() != qobj.coefficient_size()) {
376 return "indices and coefficients must have the same size";
379 for (
int i = 0; i < qobj.qvar1_index_size(); ++i) {
380 if (qobj.qvar1_index(i) >= num_vars || qobj.qvar1_index(i) < 0) {
381 return absl::StrCat(
"qvar1_index(", i,
")=", qobj.qvar1_index(i),
382 " is invalid.",
" It must be in [0, ", num_vars,
")");
385 if (qobj.qvar2_index(i) >= num_vars || qobj.qvar2_index(i) < 0) {
386 return absl::StrCat(
"qvar2_index(", i,
")=", qobj.qvar2_index(i),
387 " is invalid.",
" It must be in [0, ", num_vars,
")");
390 if (IsNanOrAbsGreaterThanOrEqual(qobj.coefficient(i),
391 abs_value_threshold)) {
392 return absl::StrCat(
"coefficient(", i,
")=", (qobj.coefficient(i)),
399 std::string FindErrorInSolutionHint(
400 const PartialVariableAssignment& solution_hint,
int num_vars,
401 double abs_value_threshold) {
402 if (solution_hint.var_index_size() != solution_hint.var_value_size()) {
403 return absl::StrCat(
"var_index_size() != var_value_size() [",
404 solution_hint.var_index_size(),
" VS ",
405 solution_hint.var_value_size());
407 std::vector<bool> var_in_hint(num_vars,
false);
408 for (
int i = 0; i < solution_hint.var_index_size(); ++i) {
409 const int var_index = solution_hint.var_index(i);
410 if (var_index >= num_vars || var_index < 0) {
411 return absl::StrCat(
"var_index(", i,
")=", var_index,
" is invalid.",
412 " It must be in [0, ", num_vars,
")");
414 if (var_in_hint[var_index]) {
415 return absl::StrCat(
"Duplicate var_index = ", var_index);
417 var_in_hint[var_index] =
true;
418 if (IsNanOrAbsGreaterThanOrEqual(solution_hint.var_value(i),
419 abs_value_threshold)) {
420 return absl::StrCat(
"var_value(", i,
")=", (solution_hint.var_value(i)),
424 return std::string();
431 template <
class NamedEntity>
432 absl::flat_hash_map<std::string, int> BuildNameToIndexMap(
433 const google::protobuf::RepeatedPtrField<NamedEntity>& entities) {
434 absl::flat_hash_map<std::string, int> out;
435 for (
int i = 0; i < entities.size(); ++i) {
442 class LazyMPModelNameToIndexMaps {
444 explicit LazyMPModelNameToIndexMaps(
const MPModelProto&
model)
447 absl::StatusOr<int> LookupName(
448 MPModelProto::Annotation::TargetType target_type,
449 const std::string&
name) {
450 const absl::flat_hash_map<std::string, int>* map =
nullptr;
451 switch (target_type) {
452 case MPModelProto::Annotation::VARIABLE_DEFAULT:
453 if (!variable_name_to_index_) {
454 variable_name_to_index_ = BuildNameToIndexMap(model_.variable());
456 map = &variable_name_to_index_.value();
458 case MPModelProto::Annotation::CONSTRAINT:
459 if (!constraint_name_to_index_) {
460 constraint_name_to_index_ = BuildNameToIndexMap(model_.constraint());
462 map = &constraint_name_to_index_.value();
464 case MPModelProto::Annotation::GENERAL_CONSTRAINT:
465 if (!general_constraint_name_to_index_) {
466 general_constraint_name_to_index_ =
467 BuildNameToIndexMap(model_.general_constraint());
469 map = &general_constraint_name_to_index_.value();
473 if (
index == -2)
return absl::NotFoundError(
"name not found");
474 if (
index == -1)
return absl::InvalidArgumentError(
"name is not unique");
479 const MPModelProto& model_;
480 std::optional<absl::flat_hash_map<std::string, int>> variable_name_to_index_;
481 std::optional<absl::flat_hash_map<std::string, int>>
482 constraint_name_to_index_;
483 std::optional<absl::flat_hash_map<std::string, int>>
484 general_constraint_name_to_index_;
488 std::string FindErrorInAnnotation(
const MPModelProto::Annotation& annotation,
489 const MPModelProto&
model,
490 LazyMPModelNameToIndexMaps* name_maps) {
492 if (!annotation.has_target_index() && !annotation.has_target_name()) {
493 return "One of target_index or target_name must be set";
495 if (!MPModelProto::Annotation::TargetType_IsValid(annotation.target_type())) {
496 return "Invalid target_type";
498 int num_entitities = -1;
499 switch (annotation.target_type()) {
500 case MPModelProto::Annotation::VARIABLE_DEFAULT:
501 num_entitities =
model.variable_size();
503 case MPModelProto::Annotation::CONSTRAINT:
504 num_entitities =
model.constraint_size();
506 case MPModelProto::Annotation::GENERAL_CONSTRAINT:
507 num_entitities =
model.general_constraint_size();
510 int target_index = -1;
511 if (annotation.has_target_index()) {
512 target_index = annotation.target_index();
513 if (target_index < 0 || target_index >= num_entitities) {
514 return "Invalid target_index";
517 if (annotation.has_target_name()) {
518 if (annotation.has_target_index()) {
523 switch (annotation.target_type()) {
524 case MPModelProto::Annotation::VARIABLE_DEFAULT:
525 name =
model.variable(target_index).name();
527 case MPModelProto::Annotation::CONSTRAINT:
528 name =
model.constraint(target_index).name();
530 case MPModelProto::Annotation::GENERAL_CONSTRAINT:
531 name =
model.general_constraint(target_index).name();
534 if (annotation.target_name() !=
name) {
535 return absl::StrFormat(
536 "target_name='%s' doesn't match the name '%s' of target_index=%d",
537 annotation.target_name(),
name, target_index);
540 const absl::StatusOr<int> index_or = name_maps->LookupName(
541 annotation.target_type(), annotation.target_name());
542 if (!index_or.ok()) {
543 return absl::StrCat(
"Bad target_name: ", index_or.status().message());
545 target_index = index_or.value();
557 const MPModelProto&
model,
double abs_value_threshold,
558 const bool accept_trivially_infeasible_bounds) {
562 if (abs_value_threshold == 0.0) {
563 abs_value_threshold = absl::GetFlag(FLAGS_model_validator_infinity);
566 if (IsNanOrAbsGreaterThanOrEqual(
model.objective_offset(),
567 abs_value_threshold)) {
568 return absl::StrCat(
"Invalid objective_offset: ",
569 (
model.objective_offset()));
571 const int num_vars =
model.variable_size();
572 const int num_cts =
model.constraint_size();
576 for (
int i = 0; i < num_vars; ++i) {
577 error = FindErrorInMPVariable(
model.variable(i), abs_value_threshold,
578 accept_trivially_infeasible_bounds);
579 if (!error.empty()) {
580 return absl::StrCat(
"In variable #", i,
": ", error,
". Variable proto: ",
586 std::vector<bool> variable_appears(num_vars,
false);
587 for (
int i = 0; i < num_cts; ++i) {
588 const MPConstraintProto& constraint =
model.constraint(i);
589 error = FindErrorInMPConstraint(constraint, &variable_appears,
591 accept_trivially_infeasible_bounds);
592 if (!error.empty()) {
594 return absl::StrCat(
"In constraint #", i,
": ", error,
". ",
595 CroppedConstraintDebugString(constraint));
600 for (
int i = 0; i <
model.general_constraint_size(); ++i) {
601 const MPGeneralConstraintProto& gen_constraint =
602 model.general_constraint(i);
604 switch (gen_constraint.general_constraint_case()) {
605 case MPGeneralConstraintProto::kIndicatorConstraint:
606 error = FindErrorInMPIndicatorConstraint(
607 model, gen_constraint.indicator_constraint(), &variable_appears,
608 abs_value_threshold, accept_trivially_infeasible_bounds);
611 case MPGeneralConstraintProto::kSosConstraint:
613 FindErrorInMPSosConstraint(
model, gen_constraint.sos_constraint(),
614 &variable_appears, abs_value_threshold);
617 case MPGeneralConstraintProto::kQuadraticConstraint:
618 error = FindErrorInMPQuadraticConstraint(
619 model, gen_constraint.quadratic_constraint(), &variable_appears,
620 abs_value_threshold, accept_trivially_infeasible_bounds);
623 case MPGeneralConstraintProto::kAbsConstraint:
625 FindErrorInMPAbsConstraint(
model, gen_constraint.abs_constraint());
628 case MPGeneralConstraintProto::kAndConstraint:
629 error = FindErrorInMPAndOrConstraint(
model,
630 gen_constraint.and_constraint());
633 case MPGeneralConstraintProto::kOrConstraint:
635 FindErrorInMPAndOrConstraint(
model, gen_constraint.or_constraint());
638 case MPGeneralConstraintProto::kMinConstraint:
639 error = FindErrorInMPMinMaxConstraint(
640 model, gen_constraint.min_constraint(), abs_value_threshold);
643 case MPGeneralConstraintProto::kMaxConstraint:
644 error = FindErrorInMPMinMaxConstraint(
645 model, gen_constraint.max_constraint(), abs_value_threshold);
648 return absl::StrCat(
"Unknown general constraint type ",
649 gen_constraint.general_constraint_case());
651 if (!error.empty()) {
652 return absl::StrCat(
"In general constraint #", i,
": ", error);
657 if (
model.has_quadratic_objective()) {
658 error = FindErrorInQuadraticObjective(
model.quadratic_objective(), num_vars,
659 abs_value_threshold);
660 if (!error.empty())
return absl::StrCat(
"In quadratic_objective: ", error);
664 error = FindErrorInSolutionHint(
model.solution_hint(), num_vars,
665 abs_value_threshold);
666 if (!error.empty()) {
667 return absl::StrCat(
"In solution_hint(): ", error);
672 LazyMPModelNameToIndexMaps name_maps(
model);
673 for (
int a = 0;
a <
model.annotation_size(); ++
a) {
674 error = FindErrorInAnnotation(
model.annotation(
a),
model, &name_maps);
675 if (!error.empty()) {
676 return absl::StrCat(
"In annotation #",
a,
": ", error);
681 return std::string();
684 std::optional<LazyMutableCopy<MPModelProto>>
689 if (!request.has_model() && !request.has_model_delta()) {
690 response->set_status(MPSOLVER_OPTIMAL);
691 response->set_status_str(
"Requests without model are considered OPTIMAL");
694 if (request.has_model() && request.has_model_delta()) {
695 response->set_status(MPSOLVER_MODEL_INVALID);
697 "Fields 'model' and 'model_delta' are mutually exclusive");
703 if (request.has_model_delta()) {
706 std::string contents;
708 request.model_delta().baseline_model_file_path(), &contents);
709 if (!file_read_status.ok()) {
710 response->set_status(MPSOLVER_MODEL_INVALID);
712 "Error when reading model_delta.baseline_model_file_path: '" +
713 file_read_status.ToString());
716 if (!
model.get_mutable()->ParseFromString(contents)) {
717 response->set_status(MPSOLVER_MODEL_INVALID);
719 absl::StrFormat(
"The contents of baseline model file '%s' couldn't "
720 "be parsed as a raw serialized MPModelProto",
721 request.model_delta().baseline_model_file_path()));
731 if (error.empty() && request.has_model_delta()) {
732 const MPModelDeltaProto&
delta = request.model_delta();
738 if (!error.empty()) {
739 if (request.enable_internal_solver_output()) {
740 LOG(ERROR) << absl::StrCat(
"Invalid model: ", error);
742 response->set_status(absl::StrContains(error,
"Infeasible")
743 ? MPSOLVER_INFEASIBLE
744 : MPSOLVER_MODEL_INVALID);
749 if (
model.get().variable_size() == 0 &&
model.get().constraint_size() == 0 &&
750 model.get().general_constraint_size() == 0) {
751 response->set_status(MPSOLVER_OPTIMAL);
752 response->set_objective_value(
model.get().objective_offset());
755 "Requests without variables and constraints are considered OPTIMAL");
759 return std::move(
model);
763 MPModelRequest* request, MPSolutionResponse*
response) {
764 std::optional<LazyMutableCopy<MPModelProto>> lazy_copy =
766 if (!lazy_copy)
return false;
767 if (lazy_copy->was_copied()) {
768 lazy_copy->get_mutable()->Swap(request->mutable_model());
777 const int num_vars =
model.variable_size();
781 FindErrorInSolutionHint(
model.solution_hint(), num_vars,
782 absl::GetFlag(FLAGS_model_validator_infinity));
783 if (!error.empty())
return absl::StrCat(
"Invalid solution_hint: ", error);
786 if (num_vars > 0 &&
model.solution_hint().var_index_size() == 0) {
787 return "Empty solution_hint.";
791 if (
model.solution_hint().var_index_size() != num_vars) {
792 return absl::StrCat(
"Partial solution_hint: only ",
793 model.solution_hint().var_index_size(),
" out of the ",
794 num_vars,
" problem variables are set.");
798 std::vector<double> var_value(num_vars);
799 for (
int i = 0; i <
model.solution_hint().var_index_size(); ++i) {
800 const int var_index =
model.solution_hint().var_index(i);
801 const double value =
model.solution_hint().var_value(i);
802 var_value[var_index] =
value;
803 const double lb =
model.variable(var_index).lower_bound();
804 const double ub =
model.variable(var_index).upper_bound();
807 return absl::StrCat(
"Variable '",
model.variable(var_index).name(),
808 "' is set to ", (
value),
809 " which is not in the variable bounds [", (lb),
", ",
810 (ub),
"] modulo a tolerance of ", (tolerance),
".");
815 for (
int cst_index = 0; cst_index <
model.constraint_size(); ++cst_index) {
816 const MPConstraintProto& constraint =
model.constraint(cst_index);
818 for (
int j = 0; j < constraint.var_index_size(); ++j) {
819 activity.
Add(constraint.coefficient(j) *
820 var_value[constraint.var_index(j)]);
822 const double lb =
model.constraint(cst_index).lower_bound();
823 const double ub =
model.constraint(cst_index).upper_bound();
827 "Constraint '",
model.constraint(cst_index).name(),
"' has activity ",
828 (activity.
Value()),
" which is not in the constraint bounds [", (lb),
829 ", ", (ub),
"] modulo a tolerance of ", (tolerance),
".");
837 const MPModelProto&
model) {
838 const double abs_value_threshold =
839 absl::GetFlag(FLAGS_model_validator_infinity);
840 int num_vars =
model.variable_size();
843 absl::flat_hash_set<int> new_var_indices;
844 int max_var_index = num_vars - 1;
845 MPVariableProto tmp_var_proto;
846 for (
const auto& pair :
delta.variable_overrides()) {
847 const int var_index = pair.first;
848 const MPVariableProto& var_override_proto = pair.second;
850 error =
"Invalid key";
851 }
else if (var_index >= num_vars) {
852 max_var_index =
std::max(max_var_index, var_index);
853 new_var_indices.insert(var_index);
855 FindErrorInMPVariable(var_override_proto, abs_value_threshold,
858 tmp_var_proto =
model.variable(var_index);
861 tmp_var_proto.MergeFrom(var_override_proto);
863 FindErrorInMPVariable(tmp_var_proto, abs_value_threshold,
866 if (!error.empty()) {
867 return absl::StrFormat(
868 "variable_overrides with key (eg. var index) = %d: %s", var_index,
872 if (max_var_index != num_vars + new_var_indices.size() - 1) {
873 return absl::StrFormat(
874 "The added and existing variable indices do not form a dense integer "
875 "interval: oldmax=%d, max=%d, num added=%d",
876 num_vars - 1, max_var_index, new_var_indices.size());
879 num_vars += new_var_indices.size();
886 std::vector<bool> variable_appears(num_vars,
false);
887 MPConstraintProto tmp_constraint_proto;
888 const int num_constraints =
model.constraint_size();
889 absl::flat_hash_set<int> new_ct_indices;
890 int max_ct_index = num_constraints - 1;
891 for (
const auto& pair :
delta.constraint_overrides()) {
892 const int ct_index = pair.first;
893 const MPConstraintProto& constraint_override_proto = pair.second;
895 error =
"Invalid constraint index";
896 }
else if (ct_index >= num_constraints) {
897 max_ct_index =
std::max(max_ct_index, ct_index);
898 new_ct_indices.insert(ct_index);
899 error = FindErrorInMPConstraint(
900 constraint_override_proto, &variable_appears, abs_value_threshold,
910 tmp_constraint_proto.Clear();
912 &tmp_constraint_proto);
913 tmp_constraint_proto.MergeFrom(constraint_override_proto);
914 error = FindErrorInMPConstraint(
915 tmp_constraint_proto, &variable_appears, abs_value_threshold,
918 if (!error.empty()) {
919 return absl::StrFormat(
920 "constraint_overrides with key (eg. constraint index) = %d: %s",
924 if (max_ct_index != num_constraints + new_ct_indices.size() - 1) {
925 return absl::StrFormat(
926 "The added and existing constraint indices do not form a dense integer "
927 "interval: oldmax=%d, max=%d, num added=%d",
928 num_constraints - 1, max_ct_index, new_ct_indices.size());
935 MPConstraintProto* to) {
936 #define COPY_FIELD_IF_PRESENT(field) \
937 if (from.has_##field()) to->set_##field(from.field())
942 #undef COPY_FIELD_IF_PRESENT
946 void PruneZeroTermsInMpConstraint(MPConstraintProto*
ct) {
950 while (first_zero < ct->var_index_size() &&
951 ct->coefficient(first_zero) != 0.0) {
954 int num_kept = first_zero;
955 for (
int i = first_zero; i <
ct->var_index_size(); ++i) {
956 if (
ct->coefficient(i) == 0.0)
continue;
958 ct->set_var_index(num_kept,
ct->var_index(i));
959 ct->set_coefficient(num_kept,
ct->coefficient(i));
963 ct->mutable_var_index()->Truncate(num_kept);
964 ct->mutable_coefficient()->Truncate(num_kept);
971 void ExtendRepeatedPtrFieldToSize(
const int size, T* repeated_messages) {
972 DCHECK_GE(size, repeated_messages->size());
973 while (repeated_messages->size() < size) repeated_messages->Add();
978 MPModelProto*
model) {
980 int max_var_index = -1;
981 for (
const auto& p :
delta.variable_overrides()) {
982 max_var_index =
std::max(max_var_index, p.first);
984 if (max_var_index >=
model->variable_size()) {
985 ExtendRepeatedPtrFieldToSize(max_var_index + 1,
model->mutable_variable());
988 for (
const auto& p :
delta.variable_overrides()) {
989 model->mutable_variable(p.first)->MergeFrom(p.second);
993 int max_ct_index = -1;
994 for (
const auto& p :
delta.constraint_overrides()) {
995 max_ct_index =
std::max(max_ct_index, p.first);
997 const int old_num_constraints =
model->constraint_size();
998 if (max_ct_index >= old_num_constraints) {
999 ExtendRepeatedPtrFieldToSize(max_ct_index + 1,
model->mutable_constraint());
1002 for (
const auto& p :
delta.constraint_overrides()) {
1003 const MPConstraintProto& override_ct = p.second;
1004 MPConstraintProto* baseline =
model->mutable_constraint(p.first);
1006 if (p.first >= old_num_constraints) {
1007 *baseline = override_ct;
1012 if (override_ct.has_lower_bound() &&
1013 override_ct.lower_bound() <=
1014 -absl::GetFlag(FLAGS_model_validator_infinity) &&
1015 override_ct.has_upper_bound() &&
1016 override_ct.upper_bound() >=
1017 absl::GetFlag(FLAGS_model_validator_infinity)) {
1018 baseline->clear_var_index();
1019 baseline->clear_coefficient();
1026 absl::flat_hash_map<int, double> term_overrides;
1027 term_overrides.reserve(override_ct.var_index_size());
1028 for (
int i = 0; i < override_ct.var_index_size(); ++i) {
1029 term_overrides[override_ct.var_index(i)] = override_ct.coefficient(i);
1031 for (
int i = 0; i < baseline->var_index_size(); ++i) {
1032 auto it = term_overrides.find(baseline->var_index(i));
1033 if (it == term_overrides.end())
continue;
1034 baseline->set_coefficient(i, it->second);
1037 PruneZeroTermsInMpConstraint(baseline);
1039 for (
const auto& p : term_overrides) {
1040 if (p.second != 0.0) {
1041 baseline->add_var_index(p.first);
1042 baseline->add_coefficient(p.second);
void Add(const FpNumber &value)
SharedResponseManager * response
ABSL_FLAG(double, model_validator_infinity, 1e100, "Anything above or equal to this magnitude will be considered infinity.")
#define COPY_FIELD_IF_PRESENT(field)
Collection::value_type::second_type & LookupOrInsert(Collection *const collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Collection of objects used to extend the Constraint Solver library.
bool ExtractValidMPModelInPlaceOrPopulateResponseStatus(MPModelRequest *request, MPSolutionResponse *response)
Like ExtractValidMPModelOrPopulateResponseStatus(), but works in-place: if the MPModel needed extract...
bool IsSmallerWithinTolerance(FloatType x, FloatType y, FloatType tolerance)
std::string FindErrorInMPModelDeltaProto(const MPModelDeltaProto &delta, const MPModelProto &model)
Like FindErrorInMPModelProto, but for a MPModelDeltaProto applied to a given baseline model (assumed ...
void ApplyVerifiedMPModelDelta(const MPModelDeltaProto &delta, MPModelProto *model)
std::string ProtobufShortDebugString(const P &message)
std::optional< LazyMutableCopy< MPModelProto > > ExtractValidMPModelOrPopulateResponseStatus(const MPModelRequest &request, MPSolutionResponse *response)
If the model is valid and non-empty, returns it (possibly after extracting the model_delta).
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.
::absl::Status PortableFileGetContents(absl::string_view file_name, std::string *output)
std::string FindFeasibilityErrorInSolutionHint(const MPModelProto &model, double tolerance)
Returns an empty string if the solution hint given in the model is a feasible solution.
void MergeMPConstraintProtoExceptTerms(const MPConstraintProto &from, MPConstraintProto *to)
std::vector< int > var_indices