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