OR-Tools  8.0
model_validator.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 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 <cmath>
18 #include <limits>
19 
20 #include "absl/container/flat_hash_map.h"
21 #include "absl/container/flat_hash_set.h"
22 #include "absl/status/status.h"
23 #include "absl/strings/str_cat.h"
24 #include "absl/strings/str_format.h"
25 #include "absl/types/optional.h"
29 #include "ortools/port/file.h"
31 #include "ortools/util/fp_utils.h"
33 
35  model_validator_infinity, 1e100,
36  "Anything above or equal to this magnitude will be considered infinity.");
37 
38 namespace operations_research {
39 namespace {
40 
41 bool IsFinite(double value) {
42  return std::isfinite(value) && value < FLAGS_model_validator_infinity &&
43  value > -FLAGS_model_validator_infinity;
44 }
45 
46 // Internal method to detect errors in bounds. The object passed as parameter
47 // must have "lower_bound" and "upper_bound" fields.
48 template <typename BoundedElement>
49 std::string FindErrorInBounds(const BoundedElement& element) {
50  if (std::isnan(element.lower_bound()) || std::isnan(element.upper_bound()) ||
51  element.lower_bound() >= FLAGS_model_validator_infinity ||
52  element.upper_bound() <= -FLAGS_model_validator_infinity ||
53  element.lower_bound() > element.upper_bound()) {
54  return absl::StrFormat("Infeasible bounds: [%f, %f]", element.lower_bound(),
55  element.upper_bound());
56  }
57  return "";
58 }
59 
60 // Internal method to detect errors in a single variable.
61 std::string FindErrorInMPVariable(const MPVariableProto& variable) {
62  const std::string bound_error = FindErrorInBounds(variable);
63  if (!bound_error.empty()) return bound_error;
64 
65  if (variable.is_integer() &&
66  ceil(variable.lower_bound()) > floor(variable.upper_bound())) {
67  return absl::StrCat(
68  "Infeasible bounds for integer variable: [", (variable.lower_bound()),
69  ", ", (variable.upper_bound()), "]", " translate to the empty set");
70  }
71  if (!IsFinite(variable.objective_coefficient())) {
72  return absl::StrCat("Invalid objective_coefficient: ",
73  (variable.objective_coefficient()));
74  }
75  return std::string();
76 }
77 
78 // Returns an error message if 'var_indices' contains a duplicate index.
79 template <typename Iterable>
80 std::string FindDuplicateVarIndex(const Iterable& var_indices,
81  std::vector<bool>* var_mask) {
82  int duplicate_var_index = -1;
83  for (const int var_index : var_indices) {
84  if ((*var_mask)[var_index]) duplicate_var_index = var_index;
85  (*var_mask)[var_index] = true;
86  }
87  // Reset "var_mask" to all false, sparsely.
88  for (const int var_index : var_indices) {
89  (*var_mask)[var_index] = false;
90  }
91  if (duplicate_var_index >= 0) {
92  return absl::StrCat("var_index #", duplicate_var_index,
93  " appears several times");
94  }
95  return "";
96 }
97 
98 // Internal method to detect errors in a single constraint.
99 // "var_mask" is a vector<bool> whose size is the number of variables in
100 // the model, and it will be all set to false before and after the call.
101 std::string FindErrorInMPConstraint(const MPConstraintProto& constraint,
102  std::vector<bool>* var_mask) {
103  const std::string bound_error = FindErrorInBounds(constraint);
104  if (!bound_error.empty()) return bound_error;
105 
106  // TODO(user): clarify explicitly, at least in a comment, whether we want
107  // to accept empty constraints (i.e. without variables).
108 
109  const int num_vars_in_model = var_mask->size();
110  const int num_vars_in_ct = constraint.var_index_size();
111  const int num_coeffs_in_ct = constraint.coefficient_size();
112  if (num_vars_in_ct != num_coeffs_in_ct) {
113  return absl::StrCat("var_index_size() != coefficient_size() (",
114  num_vars_in_ct, " VS ", num_coeffs_in_ct);
115  }
116  for (int i = 0; i < num_vars_in_ct; ++i) {
117  const int var_index = constraint.var_index(i);
118  if (var_index >= num_vars_in_model || var_index < 0) {
119  return absl::StrCat("var_index(", i, ")=", var_index,
120  " is out of bounds");
121  }
122  const double coeff = constraint.coefficient(i);
123  if (!IsFinite(coeff)) {
124  return absl::StrCat("coefficient(", i, ")=", (coeff), " is invalid");
125  }
126  }
127 
128  const std::string error =
129  FindDuplicateVarIndex(constraint.var_index(), var_mask);
130  if (!error.empty()) return error;
131 
132  // We found no error, all is fine.
133  return std::string();
134 }
135 
136 std::string CroppedConstraintDebugString(const MPConstraintProto& constraint) {
137  const int kMaxPrintedVars = 10;
138 
139  MPConstraintProto constraint_light = constraint;
140  std::string suffix_str;
141  if (constraint.var_index_size() > kMaxPrintedVars) {
142  constraint_light.mutable_var_index()->Truncate(kMaxPrintedVars);
143  absl::StrAppend(&suffix_str,
144  " (var_index cropped; size=", constraint.var_index_size(),
145  ").");
146  }
147  if (constraint.coefficient_size() > kMaxPrintedVars) {
148  constraint_light.mutable_coefficient()->Truncate(kMaxPrintedVars);
149  absl::StrAppend(&suffix_str, " (coefficient cropped; size=",
150  constraint.coefficient_size(), ").");
151  }
152  return absl::StrCat("Constraint proto: ",
153  ProtobufShortDebugString(constraint_light), suffix_str);
154 }
155 
156 bool IsBoolean(const MPVariableProto& variable) {
157  if (variable.lower_bound() < 0) return false;
158  if (variable.upper_bound() > 1) return false;
159  return variable.is_integer();
160 }
161 
162 std::string FindErrorInMPIndicatorConstraint(
163  const MPModelProto& model, const MPIndicatorConstraint& indicator,
164  std::vector<bool>* var_mask) {
165  if (!indicator.has_var_index()) {
166  return "var_index is required.";
167  }
168  const int var_index = indicator.var_index();
169  if (var_index < 0 || var_index >= model.variable_size()) {
170  return absl::StrCat("var_index=", var_index, " is out of bounds.");
171  }
172  if (!IsBoolean(model.variable(var_index))) {
173  return absl::StrCat("var_index=", var_index, " is not Boolean.");
174  }
175  const int var_value = indicator.var_value();
176  if (var_value < 0 || var_value > 1) {
177  return absl::StrCat("var_value=", var_value, " must be 0 or 1.");
178  }
179  const MPConstraintProto& constraint = indicator.constraint();
180  std::string error = FindErrorInMPConstraint(constraint, var_mask);
181  if (!error.empty()) {
182  // Constraint protos can be huge, theoretically. So we guard against
183  // that.
184  return absl::StrCat(error, " in constraint ",
185  CroppedConstraintDebugString(constraint));
186  }
187  return "";
188 }
189 
190 std::string FindErrorInMPSosConstraint(const MPModelProto& model,
191  const MPSosConstraint& sos,
192  std::vector<bool>* var_mask) {
193  if (sos.weight_size() != 0 && sos.weight_size() != sos.var_index_size()) {
194  return "weight_size() > 0 and var_index_size() != weight_size()";
195  }
196  for (const int var_index : sos.var_index()) {
197  if (var_index < 0 || var_index >= model.variable_size()) {
198  return absl::StrCat("var_index=", var_index, " is out of bounds.");
199  }
200  }
201  for (int i = 0; i < sos.weight_size(); ++i) {
202  if (!IsFinite(sos.weight(i))) {
203  return absl::StrCat("Invalid weight: ", sos.weight(i));
204  }
205  if (i == 0) continue;
206  if (sos.weight(i - 1) >= sos.weight(i)) {
207  return "SOS weights must be strictly increasing";
208  }
209  }
210 
211  const std::string error = FindDuplicateVarIndex(sos.var_index(), var_mask);
212  if (!error.empty()) return error;
213 
214  return "";
215 }
216 
217 std::string FindErrorInMPQuadraticConstraint(const MPModelProto& model,
218  const MPQuadraticConstraint& qcst,
219  std::vector<bool>* var_mask) {
220  const int num_vars = model.variable_size();
221 
222  if (qcst.var_index_size() != qcst.coefficient_size()) {
223  return "var_index_size() != coefficient_size()";
224  }
225 
226  const std::string bound_error = FindErrorInBounds(qcst);
227  if (!bound_error.empty()) return bound_error;
228 
229  for (int i = 0; i < qcst.var_index_size(); ++i) {
230  if (qcst.var_index(i) < 0 || qcst.var_index(i) >= num_vars) {
231  return absl::StrCat("var_index(", i, ")=", qcst.var_index(i),
232  " is invalid.", " It must be in [0, ", num_vars, ")");
233  }
234 
235  if (!IsFinite(qcst.coefficient(i))) {
236  return absl::StrCat("coefficient(", i, ")=", qcst.coefficient(i),
237  " is invalid");
238  }
239  }
240  const std::string duplicate_error =
241  FindDuplicateVarIndex(qcst.var_index(), var_mask);
242  if (!duplicate_error.empty()) return duplicate_error;
243 
244  if (qcst.qvar1_index_size() != qcst.qvar2_index_size() ||
245  qcst.qvar1_index_size() != qcst.qcoefficient_size()) {
246  return "quadratic indices and coefficients must have the same size";
247  }
248  for (int i = 0; i < qcst.qvar1_index_size(); ++i) {
249  if (qcst.qvar1_index(i) >= num_vars || qcst.qvar1_index(i) < 0) {
250  return absl::StrCat("qvar1_index(", i, ")=", qcst.qvar1_index(i),
251  " is invalid.", " It must be in [0, ", num_vars, ")");
252  }
253 
254  if (qcst.qvar2_index(i) >= num_vars || qcst.qvar2_index(i) < 0) {
255  return absl::StrCat("qvar2_index(", i, ")=", qcst.qvar2_index(i),
256  " is invalid.", " It must be in [0, ", num_vars, ")");
257  }
258 
259  if (!IsFinite(qcst.qcoefficient(i))) {
260  return absl::StrCat("qcoefficient(", i, ")=", qcst.qcoefficient(i),
261  " is invalid");
262  }
263  }
264 
265  return "";
266 }
267 
268 std::string FindErrorInMPAbsConstraint(const MPModelProto& model,
269  const MPAbsConstraint& abs) {
270  if (!abs.has_var_index()) {
271  return "var_index is required.";
272  }
273  if (!abs.has_resultant_var_index()) {
274  return "resultant_var_index is required.";
275  }
276 
277  const int num_vars = model.variable_size();
278  if (abs.var_index() < 0 || abs.var_index() >= num_vars) {
279  return absl::StrCat("var_index=", abs.var_index(), " is invalid.",
280  " It must be in [0, ", num_vars, ")");
281  }
282  if (abs.resultant_var_index() < 0 || abs.resultant_var_index() >= num_vars) {
283  return absl::StrCat("var_index=", abs.resultant_var_index(), " is invalid.",
284  " It must be in [0, ", num_vars, ")");
285  }
286  return "";
287 }
288 
289 std::string FindErrorInMPAndOrConstraint(const MPModelProto& model,
290  const MPArrayConstraint& and_or) {
291  if (and_or.var_index_size() == 0) {
292  return "var_index cannot be empty.";
293  }
294  if (!and_or.has_resultant_var_index()) {
295  return "resultant_var_index is required.";
296  }
297 
298  const int num_vars = model.variable_size();
299  for (int i = 0; i < and_or.var_index_size(); ++i) {
300  if (and_or.var_index(i) < 0 || and_or.var_index(i) >= num_vars) {
301  return absl::StrCat("var_index(", i, ")=", and_or.var_index(i),
302  " is invalid.", " It must be in [0, ", num_vars, ")");
303  }
304  if (!IsBoolean(model.variable(and_or.var_index(i)))) {
305  return absl::StrCat("var_index=", i, " is not Boolean.");
306  }
307  }
308  if (and_or.resultant_var_index() < 0 ||
309  and_or.resultant_var_index() >= num_vars) {
310  return absl::StrCat("resultant_var_index=", and_or.resultant_var_index(),
311  " is invalid.", " It must be in [0, ", num_vars, ")");
312  }
313  if (!IsBoolean(model.variable(and_or.resultant_var_index()))) {
314  return absl::StrCat("resultant_var_index is not Boolean.");
315  }
316  return "";
317 }
318 
319 std::string FindErrorInMPMinMaxConstraint(
320  const MPModelProto& model, const MPArrayWithConstantConstraint& min_max) {
321  if (min_max.var_index_size() == 0) {
322  return "var_index cannot be empty.";
323  }
324  if (!min_max.has_resultant_var_index()) {
325  return "resultant_var_index is required.";
326  }
327 
328  if (!IsFinite(min_max.constant())) {
329  return absl::StrCat("Invalid constant: ", (min_max.constant()));
330  }
331 
332  const int num_vars = model.variable_size();
333  for (int i = 0; i < min_max.var_index_size(); ++i) {
334  if (min_max.var_index(i) < 0 || min_max.var_index(i) >= num_vars) {
335  return absl::StrCat("var_index(", i, ")=", min_max.var_index(i),
336  " is invalid.", " It must be in [0, ", num_vars, ")");
337  }
338  }
339  if (min_max.resultant_var_index() < 0 ||
340  min_max.resultant_var_index() >= num_vars) {
341  return absl::StrCat("resultant_var_index=", min_max.resultant_var_index(),
342  " is invalid.", " It must be in [0, ", num_vars, ")");
343  }
344  return "";
345 }
346 
347 std::string FindErrorInQuadraticObjective(const MPQuadraticObjective& qobj,
348  int num_vars) {
349  if (qobj.qvar1_index_size() != qobj.qvar2_index_size() ||
350  qobj.qvar1_index_size() != qobj.coefficient_size()) {
351  return "indices and coefficients must have the same size";
352  }
353 
354  for (int i = 0; i < qobj.qvar1_index_size(); ++i) {
355  if (qobj.qvar1_index(i) >= num_vars || qobj.qvar1_index(i) < 0) {
356  return absl::StrCat("qvar1_index(", i, ")=", qobj.qvar1_index(i),
357  " is invalid.", " It must be in [0, ", num_vars, ")");
358  }
359 
360  if (qobj.qvar2_index(i) >= num_vars || qobj.qvar2_index(i) < 0) {
361  return absl::StrCat("qvar2_index(", i, ")=", qobj.qvar2_index(i),
362  " is invalid.", " It must be in [0, ", num_vars, ")");
363  }
364 
365  if (!IsFinite(qobj.coefficient(i))) {
366  return absl::StrCat("coefficient(", i, ")=", (qobj.coefficient(i)),
367  " is invalid");
368  }
369  }
370  return "";
371 }
372 
373 std::string FindErrorInSolutionHint(
374  const PartialVariableAssignment& solution_hint, int num_vars) {
375  if (solution_hint.var_index_size() != solution_hint.var_value_size()) {
376  return absl::StrCat("var_index_size() != var_value_size() [",
377  solution_hint.var_index_size(), " VS ",
378  solution_hint.var_value_size());
379  }
380  std::vector<bool> var_in_hint(num_vars, false);
381  for (int i = 0; i < solution_hint.var_index_size(); ++i) {
382  const int var_index = solution_hint.var_index(i);
383  if (var_index >= num_vars || var_index < 0) {
384  return absl::StrCat("var_index(", i, ")=", var_index, " is invalid.",
385  " It must be in [0, ", num_vars, ")");
386  }
387  if (var_in_hint[var_index]) {
388  return absl::StrCat("Duplicate var_index = ", var_index);
389  }
390  var_in_hint[var_index] = true;
391  if (!IsFinite(solution_hint.var_value(i))) {
392  return absl::StrCat("var_value(", i, ")=", (solution_hint.var_value(i)),
393  " is invalid");
394  }
395  }
396  return std::string();
397 }
398 } // namespace
399 
400 std::string FindErrorInMPModelProto(const MPModelProto& model) {
401  // NOTE(user): Empty models are considered fine by this function, although
402  // it is not clear whether MPSolver::Solve() will always respond in the same
403  // way, depending on the solvers.
404 
405  if (!IsFinite(model.objective_offset())) {
406  return absl::StrCat("Invalid objective_offset: ",
407  (model.objective_offset()));
408  }
409  const int num_vars = model.variable_size();
410  const int num_cts = model.constraint_size();
411 
412  // Validate variables.
413  std::string error;
414  for (int i = 0; i < num_vars; ++i) {
415  error = FindErrorInMPVariable(model.variable(i));
416  if (!error.empty()) {
417  return absl::StrCat("In variable #", i, ": ", error, ". Variable proto: ",
418  ProtobufShortDebugString(model.variable(i)));
419  }
420  }
421 
422  // Validate constraints.
423  std::vector<bool> variable_appears(num_vars, false);
424  for (int i = 0; i < num_cts; ++i) {
425  const MPConstraintProto& constraint = model.constraint(i);
426  error = FindErrorInMPConstraint(constraint, &variable_appears);
427  if (!error.empty()) {
428  // Constraint protos can be huge, theoretically. So we guard against that.
429  return absl::StrCat("In constraint #", i, ": ", error, ". ",
430  CroppedConstraintDebugString(constraint));
431  }
432  }
433 
434  // Validate general constraints.
435  for (int i = 0; i < model.general_constraint_size(); ++i) {
436  const MPGeneralConstraintProto& gen_constraint =
437  model.general_constraint(i);
438  std::string error;
439  switch (gen_constraint.general_constraint_case()) {
440  case MPGeneralConstraintProto::kIndicatorConstraint:
441  error = FindErrorInMPIndicatorConstraint(
442  model, gen_constraint.indicator_constraint(), &variable_appears);
443  break;
444 
445  case MPGeneralConstraintProto::kSosConstraint:
446  error = FindErrorInMPSosConstraint(
447  model, gen_constraint.sos_constraint(), &variable_appears);
448  break;
449 
450  case MPGeneralConstraintProto::kQuadraticConstraint:
451  error = FindErrorInMPQuadraticConstraint(
452  model, gen_constraint.quadratic_constraint(), &variable_appears);
453  break;
454 
455  case MPGeneralConstraintProto::kAbsConstraint:
456  error =
457  FindErrorInMPAbsConstraint(model, gen_constraint.abs_constraint());
458  break;
459 
460  case MPGeneralConstraintProto::kAndConstraint:
461  error = FindErrorInMPAndOrConstraint(model,
462  gen_constraint.and_constraint());
463  break;
464 
465  case MPGeneralConstraintProto::kOrConstraint:
466  error =
467  FindErrorInMPAndOrConstraint(model, gen_constraint.or_constraint());
468  break;
469 
470  case MPGeneralConstraintProto::kMinConstraint:
471  error = FindErrorInMPMinMaxConstraint(model,
472  gen_constraint.min_constraint());
473  break;
474 
475  case MPGeneralConstraintProto::kMaxConstraint:
476  error = FindErrorInMPMinMaxConstraint(model,
477  gen_constraint.max_constraint());
478  break;
479  default:
480  return absl::StrCat("Unknown general constraint type ",
481  gen_constraint.general_constraint_case());
482  }
483  if (!error.empty()) {
484  return absl::StrCat("In general constraint #", i, ": ", error);
485  }
486  }
487 
488  // Validate objectives.
489  if (model.has_quadratic_objective()) {
490  error =
491  FindErrorInQuadraticObjective(model.quadratic_objective(), num_vars);
492  if (!error.empty()) return absl::StrCat("In quadratic_objective: ", error);
493  }
494 
495  // Validate the solution hint.
496  error = FindErrorInSolutionHint(model.solution_hint(), num_vars);
497  if (!error.empty()) {
498  return absl::StrCat("In solution_hint(): ", error);
499  }
500 
501  return std::string();
502 }
503 
504 absl::optional<LazyMutableCopy<MPModelProto>>
505 ExtractValidMPModelOrPopulateResponseStatus(const MPModelRequest& request,
506  MPSolutionResponse* response) {
507  CHECK(response != nullptr);
508 
509  if (!request.has_model() && !request.has_model_delta()) {
510  response->set_status(MPSOLVER_OPTIMAL);
511  response->set_status_str("Requests without model are considered OPTIMAL");
512  return absl::nullopt;
513  }
514  if (request.has_model() && request.has_model_delta()) {
515  response->set_status(MPSOLVER_MODEL_INVALID);
516  response->set_status_str(
517  "Fields 'model' and 'model_delta' are mutually exclusive");
518  return absl::nullopt;
519  }
520 
521  // Extract the baseline model.
522  LazyMutableCopy<MPModelProto> model(request.model());
523  if (request.has_model_delta()) {
524  // NOTE(user): This library needs to be portable, so we can't include
525  // ortools/base/file.h; see ../port/file.h.
526  std::string contents;
527  const absl::Status file_read_status = PortableFileGetContents(
528  request.model_delta().baseline_model_file_path(), &contents);
529  if (!file_read_status.ok()) {
530  response->set_status(MPSOLVER_MODEL_INVALID);
531  response->set_status_str(
532  "Error when reading model_delta.baseline_model_file_path: '" +
533  file_read_status.ToString());
534  return absl::nullopt;
535  }
536  if (!model.get_mutable()->ParseFromString(contents)) {
537  response->set_status(MPSOLVER_MODEL_INVALID);
538  response->set_status_str(
539  absl::StrFormat("The contents of baseline model file '%s' couldn't "
540  "be parsed as a raw serialized MPModelProto",
541  request.model_delta().baseline_model_file_path()));
542  return absl::nullopt;
543  }
544  }
545 
546  // Validate the baseline model.
547  std::string error = FindErrorInMPModelProto(model.get());
548 
549  // If the baseline is valid and we have a model delta, validate the delta,
550  // then apply it.
551  if (error.empty() && request.has_model_delta()) {
552  const MPModelDeltaProto& delta = request.model_delta();
553  error = FindErrorInMPModelDeltaProto(delta, model.get());
554  if (error.empty()) ApplyVerifiedMPModelDelta(delta, model.get_mutable());
555  }
556 
557  // Deal with errors.
558  if (!error.empty()) {
559  if (request.enable_internal_solver_output()) {
560  LOG(ERROR) << absl::StrCat("Invalid model: ", error);
561  }
562  response->set_status(error.find("Infeasible") == std::string::npos
565  response->set_status_str(error);
566  return absl::nullopt;
567  }
568 
569  if (model.get().variable_size() == 0 && model.get().constraint_size() == 0 &&
570  model.get().general_constraint_size() == 0) {
571  response->set_status(MPSOLVER_OPTIMAL);
572  response->set_objective_value(model.get().objective_offset());
573  response->set_best_objective_bound(response->objective_value());
574  response->set_status_str(
575  "Requests without variables and constraints are considered OPTIMAL");
576  return absl::nullopt;
577  }
578 
579  return std::move(model);
580 }
581 
583  MPModelRequest* request, MPSolutionResponse* response) {
584  absl::optional<LazyMutableCopy<MPModelProto>> lazy_copy =
586  if (!lazy_copy) return false;
587  if (lazy_copy->was_copied()) {
588  lazy_copy->get_mutable()->Swap(request->mutable_model());
589  }
590  return true;
591 }
592 
593 // TODO(user): Add a general FindFeasibilityErrorInSolution() and factor out the
594 // common code.
595 std::string FindFeasibilityErrorInSolutionHint(const MPModelProto& model,
596  double tolerance) {
597  const int num_vars = model.variable_size();
598 
599  // First, we validate the solution hint.
600  std::string error = FindErrorInSolutionHint(model.solution_hint(), num_vars);
601  if (!error.empty()) return absl::StrCat("Invalid solution_hint: ", error);
602 
603  // Special error message for the empty case.
604  if (num_vars > 0 && model.solution_hint().var_index_size() == 0) {
605  return "Empty solution_hint.";
606  }
607 
608  // To be feasible, the hint must not be partial.
609  if (model.solution_hint().var_index_size() != num_vars) {
610  return absl::StrCat("Partial solution_hint: only ",
611  model.solution_hint().var_index_size(), " out of the ",
612  num_vars, " problem variables are set.");
613  }
614 
615  // All the values must be exactly in the variable bounds.
616  std::vector<double> var_value(num_vars);
617  for (int i = 0; i < model.solution_hint().var_index_size(); ++i) {
618  const int var_index = model.solution_hint().var_index(i);
619  const double value = model.solution_hint().var_value(i);
620  var_value[var_index] = value;
621  const double lb = model.variable(var_index).lower_bound();
622  const double ub = model.variable(var_index).upper_bound();
623  if (!IsSmallerWithinTolerance(value, ub, tolerance) ||
624  !IsSmallerWithinTolerance(lb, value, tolerance)) {
625  return absl::StrCat("Variable '", model.variable(var_index).name(),
626  "' is set to ", (value),
627  " which is not in the variable bounds [", (lb), ", ",
628  (ub), "] modulo a tolerance of ", (tolerance), ".");
629  }
630  }
631 
632  // All the constraints must be satisfiable.
633  for (int cst_index = 0; cst_index < model.constraint_size(); ++cst_index) {
634  const MPConstraintProto& constraint = model.constraint(cst_index);
635  AccurateSum<double> activity;
636  for (int j = 0; j < constraint.var_index_size(); ++j) {
637  activity.Add(constraint.coefficient(j) *
638  var_value[constraint.var_index(j)]);
639  }
640  const double lb = model.constraint(cst_index).lower_bound();
641  const double ub = model.constraint(cst_index).upper_bound();
642  if (!IsSmallerWithinTolerance(activity.Value(), ub, tolerance) ||
643  !IsSmallerWithinTolerance(lb, activity.Value(), tolerance)) {
644  return absl::StrCat(
645  "Constraint '", model.constraint(cst_index).name(), "' has activity ",
646  (activity.Value()), " which is not in the constraint bounds [", (lb),
647  ", ", (ub), "] modulo a tolerance of ", (tolerance), ".");
648  }
649  }
650 
651  return "";
652 }
653 
654 std::string FindErrorInMPModelDeltaProto(const MPModelDeltaProto& delta,
655  const MPModelProto& model) {
656  int num_vars = model.variable_size();
657  // Validate delta variables.
658  std::string error;
659  absl::flat_hash_set<int> new_var_indices;
660  int max_var_index = num_vars - 1;
661  MPVariableProto tmp_var_proto;
662  for (const auto& pair : delta.variable_overrides()) {
663  const int var_index = pair.first;
664  const MPVariableProto& var_override_proto = pair.second;
665  if (var_index < 0) {
666  error = "Invalid key";
667  } else if (var_index >= num_vars) {
668  max_var_index = std::max(max_var_index, var_index);
669  new_var_indices.insert(var_index);
670  error = FindErrorInMPVariable(var_override_proto);
671  } else {
672  tmp_var_proto = model.variable(var_index);
673  // NOTE(user): It is OK for the override proto to be empty, i.e. be a
674  // non-override.
675  tmp_var_proto.MergeFrom(var_override_proto);
676  error = FindErrorInMPVariable(tmp_var_proto);
677  }
678  if (!error.empty()) {
679  return absl::StrFormat(
680  "variable_overrides with key (eg. var index) = %d: %s", var_index,
681  error);
682  }
683  }
684  if (max_var_index != num_vars + new_var_indices.size() - 1) {
685  return absl::StrFormat(
686  "The added and existing variable indices do not form a dense integer "
687  "interval: oldmax=%d, max=%d, num added=%d",
688  num_vars - 1, max_var_index, new_var_indices.size());
689  }
690  // Now we "officially" add the new variables to "num_vars".
691  num_vars += new_var_indices.size();
692 
693  // Validate delta constraints. We can avoid going over the full
694  // var_index/coefficient of the original constraint, since the overrides are
695  // self-sufficient (i.e. the override var_index/coefficients are valid iff
696  // they would be valid in a standalone, new constraint). So we use a partial
697  // proto merger to avoid those in the baseline constraint.
698  std::vector<bool> variable_appears(num_vars, false);
699  MPConstraintProto tmp_constraint_proto;
700  const int num_constraints = model.constraint_size();
701  absl::flat_hash_set<int> new_ct_indices;
702  int max_ct_index = num_constraints - 1;
703  for (const auto& pair : delta.constraint_overrides()) {
704  const int ct_index = pair.first;
705  const MPConstraintProto& constraint_override_proto = pair.second;
706  if (ct_index < 0) {
707  error = "Invalid constraint index";
708  } else if (ct_index >= num_constraints) {
709  max_ct_index = std::max(max_ct_index, ct_index);
710  new_ct_indices.insert(ct_index);
711  error =
712  FindErrorInMPConstraint(constraint_override_proto, &variable_appears);
713  } else {
714  // NOTE(user): We don't need to do the merging of var_index/coefficient:
715  // that part of the merged constraint will be valid iff the override is
716  // valid as a standalone var_index/coefficient map.
717  // So we simply validate a reduced version of the actual "merged"
718  // constraint, by removing the var_index/coefficient of the baseline.
719  // Benefit: the complexity is O(|constraint override|) even if the
720  // baseline constraint was huge.
721  tmp_constraint_proto.Clear();
722  MergeMPConstraintProtoExceptTerms(model.constraint(ct_index),
723  &tmp_constraint_proto);
724  tmp_constraint_proto.MergeFrom(constraint_override_proto);
725  error = FindErrorInMPConstraint(tmp_constraint_proto, &variable_appears);
726  }
727  if (!error.empty()) {
728  return absl::StrFormat(
729  "constraint_overrides with key (eg. constraint index) = %d: %s",
730  ct_index, error);
731  }
732  }
733  if (max_ct_index != num_constraints + new_ct_indices.size() - 1) {
734  return absl::StrFormat(
735  "The added and existing constraint indices do not form a dense integer "
736  "interval: oldmax=%d, max=%d, num added=%d",
737  num_constraints - 1, max_ct_index, new_ct_indices.size());
738  }
739 
740  return "";
741 }
742 
743 void MergeMPConstraintProtoExceptTerms(const MPConstraintProto& from,
744  MPConstraintProto* to) {
745 #define COPY_FIELD_IF_PRESENT(field) \
746  if (from.has_##field()) to->set_##field(from.field())
747  COPY_FIELD_IF_PRESENT(lower_bound);
748  COPY_FIELD_IF_PRESENT(upper_bound);
750  COPY_FIELD_IF_PRESENT(is_lazy);
751 #undef COPY_FIELD_IF_PRESENT
752 }
753 
754 namespace {
755 void PruneZeroTermsInMpConstraint(MPConstraintProto* ct) {
756  // Optimize the fast path (when no term is pruned) by doing a first quick scan
757  // until the first zero.
758  int first_zero = 0;
759  while (first_zero < ct->var_index_size() &&
760  ct->coefficient(first_zero) != 0.0) {
761  ++first_zero;
762  }
763  int num_kept = first_zero;
764  for (int i = first_zero; i < ct->var_index_size(); ++i) {
765  if (ct->coefficient(i) == 0.0) continue;
766  if (num_kept != i) {
767  ct->set_var_index(num_kept, ct->var_index(i));
768  ct->set_coefficient(num_kept, ct->coefficient(i));
769  }
770  ++num_kept;
771  }
772  ct->mutable_var_index()->Truncate(num_kept);
773  ct->mutable_coefficient()->Truncate(num_kept);
774 }
775 
776 // Adds default entries to a repeated message field until it has the wanted
777 // size. We don't use google::protobuf::util::Resize() because it's not
778 // compatible with 'light' protos.
779 template <class T>
780 void ExtendRepeatedPtrFieldToSize(const int size, T* repeated_messages) {
781  DCHECK_GE(size, repeated_messages->size());
782  while (repeated_messages->size() < size) repeated_messages->Add();
783 }
784 } // namespace
785 
786 void ApplyVerifiedMPModelDelta(const MPModelDeltaProto& delta,
787  MPModelProto* model) {
788  // Apply the delta to the variables: first, resize the variable array.
789  int max_var_index = -1;
790  for (const auto& p : delta.variable_overrides()) {
791  max_var_index = std::max(max_var_index, p.first);
792  }
793  if (max_var_index >= model->variable_size()) {
794  ExtendRepeatedPtrFieldToSize(max_var_index + 1, model->mutable_variable());
795  }
796  // Then, apply the variable overrides.
797  for (const auto& p : delta.variable_overrides()) {
798  model->mutable_variable(p.first)->MergeFrom(p.second);
799  }
800 
801  // Apply the delta to the constraints: first, resize the constraint array.
802  int max_ct_index = -1;
803  for (const auto& p : delta.constraint_overrides()) {
804  max_ct_index = std::max(max_ct_index, p.first);
805  }
806  const int old_num_constraints = model->constraint_size();
807  if (max_ct_index >= old_num_constraints) {
808  ExtendRepeatedPtrFieldToSize(max_ct_index + 1, model->mutable_constraint());
809  }
810  // Then, apply the constraint overrides.
811  for (const auto& p : delta.constraint_overrides()) {
812  const MPConstraintProto& override_ct = p.second;
813  MPConstraintProto* baseline = model->mutable_constraint(p.first);
814  // Fast path for added constraints.
815  if (p.first >= old_num_constraints) {
816  *baseline = override_ct;
817  continue;
818  }
819  MergeMPConstraintProtoExceptTerms(/*from=*/override_ct, /*to=*/baseline);
820  // Special case: the override is neutralized.
821  if (override_ct.has_lower_bound() &&
822  override_ct.lower_bound() <= -FLAGS_model_validator_infinity &&
823  override_ct.has_upper_bound() &&
824  override_ct.upper_bound() >= FLAGS_model_validator_infinity) {
825  baseline->clear_var_index();
826  baseline->clear_coefficient();
827  continue;
828  }
829  // Otherwise we have to apply the term overrides. We can't do that in less
830  // than O(|baseline| + |override_ct|) because the baseline doesn't have a
831  // lookup-friendly data structure. But we still try to do it as efficiently
832  // as possible. In particular, we only use O(|override_ct|) extra memory.
833  absl::flat_hash_map<int, double> term_overrides;
834  term_overrides.reserve(override_ct.var_index_size());
835  for (int i = 0; i < override_ct.var_index_size(); ++i) {
836  term_overrides[override_ct.var_index(i)] = override_ct.coefficient(i);
837  }
838  for (int i = 0; i < baseline->var_index_size(); ++i) {
839  auto it = term_overrides.find(baseline->var_index(i));
840  if (it == term_overrides.end()) continue;
841  baseline->set_coefficient(i, it->second);
842  it->second = 0.0; // To mark this term override as 'has been applied'.
843  }
844  PruneZeroTermsInMpConstraint(baseline);
845  // Add the term overrides which haven't been used: those are added terms.
846  for (const auto& p : term_overrides) {
847  if (p.second != 0.0) {
848  baseline->add_var_index(p.first);
849  baseline->add_coefficient(p.second);
850  }
851  }
852  }
853 }
854 
855 } // namespace operations_research
operations_research::AccurateSum::Value
FpNumber Value() const
Definition: accurate_sum.h:37
response
SharedResponseManager * response
Definition: cp_model_solver.cc:2027
max
int64 max
Definition: alldiff_cst.cc:139
linear_solver.pb.h
operations_research::PortableFileGetContents
::absl::Status PortableFileGetContents(absl::string_view file_name, std::string *output)
Definition: file_nonport.cc:32
proto_utils.h
operations_research::AccurateSum::Add
void Add(const FpNumber &value)
Definition: accurate_sum.h:29
var_indices
std::vector< int > var_indices
Definition: sat/lp_utils.cc:286
value
int64 value
Definition: demon_profiler.cc:43
operations_research
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
Definition: dense_doubly_linked_list.h:21
accurate_sum.h
file.h
operations_research::AccurateSum
Definition: accurate_sum.h:23
operations_research::ExtractValidMPModelOrPopulateResponseStatus
absl::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).
Definition: model_validator.cc:505
operations_research::FindErrorInMPModelDeltaProto
std::string FindErrorInMPModelDeltaProto(const MPModelDeltaProto &delta, const MPModelProto &model)
Like FindErrorInMPModelProto, but for a MPModelDeltaProto applied to a given baseline model (assumed ...
Definition: model_validator.cc:654
operations_research::LazyMutableCopy
Definition: lazy_mutable_copy.h:45
operations_research::glop::IsFinite
bool IsFinite(Fractional value)
Definition: lp_types.h:90
operations_research::ExtractValidMPModelInPlaceOrPopulateResponseStatus
bool ExtractValidMPModelInPlaceOrPopulateResponseStatus(MPModelRequest *request, MPSolutionResponse *response)
Like ExtractValidMPModelOrPopulateResponseStatus(), but works in-place: if the MPModel needed extract...
Definition: model_validator.cc:582
fp_utils.h
COPY_FIELD_IF_PRESENT
#define COPY_FIELD_IF_PRESENT(field)
ct
const Constraint * ct
Definition: demon_profiler.cc:42
operations_research::FindFeasibilityErrorInSolutionHint
std::string FindFeasibilityErrorInSolutionHint(const MPModelProto &model, double tolerance)
Returns an empty string if the solution hint given in the model is a feasible solution.
Definition: model_validator.cc:595
model_validator.h
DEFINE_double
DEFINE_double(model_validator_infinity, 1e100, "Anything above or equal to this magnitude will be considered infinity.")
model
GRBmodel * model
Definition: gurobi_interface.cc:195
operations_research::MergeMPConstraintProtoExceptTerms
void MergeMPConstraintProtoExceptTerms(const MPConstraintProto &from, MPConstraintProto *to)
Definition: model_validator.cc:743
operations_research::MPSOLVER_MODEL_INVALID
@ MPSOLVER_MODEL_INVALID
Definition: linear_solver.pb.h:237
operations_research::ProtobufShortDebugString
std::string ProtobufShortDebugString(const P &message)
Definition: port/proto_utils.h:58
operations_research::MPSOLVER_INFEASIBLE
@ MPSOLVER_INFEASIBLE
Definition: linear_solver.pb.h:231
operations_research::ApplyVerifiedMPModelDelta
void ApplyVerifiedMPModelDelta(const MPModelDeltaProto &delta, MPModelProto *model)
Definition: model_validator.cc:786
lazy_mutable_copy.h
delta
int64 delta
Definition: resource.cc:1684
operations_research::MPSOLVER_OPTIMAL
@ MPSOLVER_OPTIMAL
Definition: linear_solver.pb.h:229
operations_research::IsSmallerWithinTolerance
bool IsSmallerWithinTolerance(FloatType x, FloatType y, FloatType tolerance)
Definition: fp_utils.h:152
operations_research::FindErrorInMPModelProto
std::string FindErrorInMPModelProto(const MPModelProto &model)
Returns an empty string iff the model is valid and not trivially infeasible.
Definition: model_validator.cc:400
commandlineflags.h
name
const std::string name
Definition: default_search.cc:807