OR-Tools  8.1
cp_model_checker.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 <memory>
18 #include <utility>
19 
20 #include "absl/container/flat_hash_map.h"
21 #include "absl/container/flat_hash_set.h"
22 #include "absl/strings/str_cat.h"
23 #include "ortools/base/hash.h"
24 #include "ortools/base/logging.h"
25 #include "ortools/base/map_util.h"
31 
32 namespace operations_research {
33 namespace sat {
34 namespace {
35 
36 // =============================================================================
37 // CpModelProto validation.
38 // =============================================================================
39 
40 // If the string returned by "statement" is not empty, returns it.
41 #define RETURN_IF_NOT_EMPTY(statement) \
42  do { \
43  const std::string error_message = statement; \
44  if (!error_message.empty()) return error_message; \
45  } while (false)
46 
47 template <typename ProtoWithDomain>
48 bool DomainInProtoIsValid(const ProtoWithDomain& proto) {
49  if (proto.domain().size() % 2) return false;
50  std::vector<ClosedInterval> domain;
51  for (int i = 0; i < proto.domain_size(); i += 2) {
52  if (proto.domain(i) > proto.domain(i + 1)) return false;
53  domain.push_back({proto.domain(i), proto.domain(i + 1)});
54  }
55  return IntervalsAreSortedAndNonAdjacent(domain);
56 }
57 
58 bool VariableReferenceIsValid(const CpModelProto& model, int reference) {
59  // We do it this way to avoid overflow if reference is kint64min for instance.
60  if (reference >= model.variables_size()) return false;
61  return reference >= -static_cast<int>(model.variables_size());
62 }
63 
64 bool LiteralReferenceIsValid(const CpModelProto& model, int reference) {
65  if (!VariableReferenceIsValid(model, reference)) return false;
66  const auto& var_proto = model.variables(PositiveRef(reference));
67  const int64 min_domain = var_proto.domain(0);
68  const int64 max_domain = var_proto.domain(var_proto.domain_size() - 1);
69  return min_domain >= 0 && max_domain <= 1;
70 }
71 
72 std::string ValidateIntegerVariable(const CpModelProto& model, int v) {
73  const IntegerVariableProto& proto = model.variables(v);
74  if (proto.domain_size() == 0) {
75  return absl::StrCat("var #", v,
76  " has no domain(): ", ProtobufShortDebugString(proto));
77  }
78  if (proto.domain_size() % 2 != 0) {
79  return absl::StrCat("var #", v, " has an odd domain() size: ",
81  }
82  if (!DomainInProtoIsValid(proto)) {
83  return absl::StrCat("var #", v, " has and invalid domain() format: ",
85  }
86 
87  // Internally, we often take the negation of a domain, and we also want to
88  // have sentinel values greater than the min/max of a variable domain, so
89  // the domain must fall in [kint64min + 2, kint64max - 1].
90  const int64 lb = proto.domain(0);
91  const int64 ub = proto.domain(proto.domain_size() - 1);
92  if (lb < kint64min + 2 || ub > kint64max - 1) {
93  return absl::StrCat(
94  "var #", v, " domain do not fall in [kint64min + 2, kint64max - 1]. ",
96  }
97 
98  // We do compute ub - lb in some place in the code and do not want to deal
99  // with overflow everywhere. This seems like a reasonable precondition anyway.
100  if (lb < 0 && lb + kint64max < ub) {
101  return absl::StrCat(
102  "var #", v,
103  " has a domain that is too large, i.e. |UB - LB| overflow an int64: ",
105  }
106 
107  return "";
108 }
109 
110 std::string ValidateArgumentReferencesInConstraint(const CpModelProto& model,
111  int c) {
112  const ConstraintProto& ct = model.constraints(c);
113  IndexReferences references = GetReferencesUsedByConstraint(ct);
114  for (const int v : references.variables) {
115  if (!VariableReferenceIsValid(model, v)) {
116  return absl::StrCat("Out of bound integer variable ", v,
117  " in constraint #", c, " : ",
119  }
120  }
121  for (const int lit : ct.enforcement_literal()) {
122  if (!LiteralReferenceIsValid(model, lit)) {
123  return absl::StrCat("Invalid enforcement literal ", lit,
124  " in constraint #", c, " : ",
126  }
127  }
128  for (const int lit : references.literals) {
129  if (!LiteralReferenceIsValid(model, lit)) {
130  return absl::StrCat("Invalid literal ", lit, " in constraint #", c, " : ",
132  }
133  }
134  for (const int i : UsedIntervals(ct)) {
135  if (i < 0 || i >= model.constraints_size()) {
136  return absl::StrCat("Out of bound interval ", i, " in constraint #", c,
137  " : ", ProtobufShortDebugString(ct));
138  }
139  if (model.constraints(i).constraint_case() !=
140  ConstraintProto::ConstraintCase::kInterval) {
141  return absl::StrCat(
142  "Interval ", i,
143  " does not refer to an interval constraint. Problematic constraint #",
144  c, " : ", ProtobufShortDebugString(ct));
145  }
146  }
147  return "";
148 }
149 
150 template <class LinearExpressionProto>
151 bool PossibleIntegerOverflow(const CpModelProto& model,
152  const LinearExpressionProto& proto) {
153  int64 sum_min = 0;
154  int64 sum_max = 0;
155  for (int i = 0; i < proto.vars_size(); ++i) {
156  const int ref = proto.vars(i);
157  const auto& var_proto = model.variables(PositiveRef(ref));
158  const int64 min_domain = var_proto.domain(0);
159  const int64 max_domain = var_proto.domain(var_proto.domain_size() - 1);
160  if (proto.coeffs(i) == kint64min) return true;
161  const int64 coeff = RefIsPositive(ref) ? proto.coeffs(i) : -proto.coeffs(i);
162  const int64 prod1 = CapProd(min_domain, coeff);
163  const int64 prod2 = CapProd(max_domain, coeff);
164 
165  // Note that we use min/max with zero to disallow "alternative" terms and
166  // be sure that we cannot have an overflow if we do the computation in a
167  // different order.
168  sum_min = CapAdd(sum_min, std::min(int64{0}, std::min(prod1, prod2)));
169  sum_max = CapAdd(sum_max, std::max(int64{0}, std::max(prod1, prod2)));
170  for (const int64 v : {prod1, prod2, sum_min, sum_max}) {
171  if (v == kint64max || v == kint64min) return true;
172  }
173  }
174 
175  // In addition to computing the min/max possible sum, we also often compare
176  // it with the constraint bounds, so we do not want max - min to overflow.
177  if (sum_min < 0 && sum_min + kint64max < sum_max) {
178  return true;
179  }
180  return false;
181 }
182 
183 std::string ValidateIntervalConstraint(const CpModelProto& model,
184  const ConstraintProto& ct) {
185  const IntervalConstraintProto& arg = ct.interval();
186  if (arg.size() < 0) {
187  const IntegerVariableProto& size_var_proto =
188  model.variables(NegatedRef(arg.size()));
189  if (size_var_proto.domain(size_var_proto.domain_size() - 1) > 0) {
190  return absl::StrCat(
191  "Negative value in interval size domain: ", ProtobufDebugString(ct),
192  "negation of size var: ", ProtobufDebugString(size_var_proto));
193  }
194  } else {
195  const IntegerVariableProto& size_var_proto = model.variables(arg.size());
196  if (size_var_proto.domain(0) < 0) {
197  return absl::StrCat(
198  "Negative value in interval size domain: ", ProtobufDebugString(ct),
199  "size var: ", ProtobufDebugString(size_var_proto));
200  }
201  }
202  return "";
203 }
204 
205 std::string ValidateLinearConstraint(const CpModelProto& model,
206  const ConstraintProto& ct) {
207  const LinearConstraintProto& arg = ct.linear();
208  if (PossibleIntegerOverflow(model, arg)) {
209  return "Possible integer overflow in constraint: " +
211  }
212  return "";
213 }
214 
215 std::string ValidateTableConstraint(const CpModelProto& model,
216  const ConstraintProto& ct) {
217  const TableConstraintProto& arg = ct.table();
218  if (arg.vars().empty()) return "";
219  if (arg.values().size() % arg.vars().size() != 0) {
220  return absl::StrCat(
221  "The flat encoding of a table constraint must be a multiple of the "
222  "number of variable: ",
224  }
225  return "";
226 }
227 
228 std::string ValidateLinearExpression(const CpModelProto& model,
229  const LinearExpressionProto& expr) {
230  if (expr.coeffs_size() != expr.vars_size()) {
231  return absl::StrCat("coeffs_size() != vars_size() in linear expression: ",
233  }
234  if (PossibleIntegerOverflow(model, expr)) {
235  return absl::StrCat("Possible overflow in linear expression: ",
237  }
238  return "";
239 }
240 
241 std::string ValidateCircuitConstraint(const CpModelProto& model,
242  const ConstraintProto& ct) {
243  const int size = ct.circuit().tails().size();
244  if (ct.circuit().heads().size() != size ||
245  ct.circuit().literals().size() != size) {
246  return absl::StrCat("Wrong field sizes in circuit: ",
248  }
249  return "";
250 }
251 
252 std::string ValidateRoutesConstraint(const CpModelProto& model,
253  const ConstraintProto& ct) {
254  const int size = ct.routes().tails().size();
255  if (ct.routes().heads().size() != size ||
256  ct.routes().literals().size() != size) {
257  return absl::StrCat("Wrong field sizes in routes: ",
259  }
260  return "";
261 }
262 
263 std::string ValidateNoOverlap2DConstraint(const CpModelProto& model,
264  const ConstraintProto& ct) {
265  const int size_x = ct.no_overlap_2d().x_intervals().size();
266  const int size_y = ct.no_overlap_2d().y_intervals().size();
267  if (size_x != size_y) {
268  return absl::StrCat("The two lists of intervals must have the same size: ",
270  }
271  return "";
272 }
273 
274 std::string ValidateAutomatonConstraint(const CpModelProto& model,
275  const ConstraintProto& ct) {
276  const int num_transistions = ct.automaton().transition_tail().size();
277  if (num_transistions != ct.automaton().transition_head().size() ||
278  num_transistions != ct.automaton().transition_label().size()) {
279  return absl::StrCat(
280  "The transitions repeated fields must have the same size: ",
282  }
283  return "";
284 }
285 
286 std::string ValidateReservoirConstraint(const CpModelProto& model,
287  const ConstraintProto& ct) {
288  if (ct.enforcement_literal_size() > 0) {
289  return "Reservoir does not support enforcement literals.";
290  }
291  if (ct.reservoir().times().size() != ct.reservoir().demands().size()) {
292  return absl::StrCat("Times and demands fields must be of the same size: ",
294  }
295  for (const int t : ct.reservoir().times()) {
296  const IntegerVariableProto& time = model.variables(t);
297  for (const int64 bound : time.domain()) {
298  if (bound < 0) {
299  return absl::StrCat("Time variables must be >= 0 in constraint ",
301  }
302  }
303  }
304  int64 sum_abs = 0;
305  for (const int64 demand : ct.reservoir().demands()) {
306  sum_abs = CapAdd(sum_abs, std::abs(demand));
307  if (sum_abs == kint64max) {
308  return "Possible integer overflow in constraint: " +
310  }
311  }
312  if (ct.reservoir().actives_size() > 0 &&
313  ct.reservoir().actives_size() != ct.reservoir().times_size()) {
314  return "Wrong array length of actives variables";
315  }
316  if (ct.reservoir().demands_size() > 0 &&
317  ct.reservoir().demands_size() != ct.reservoir().times_size()) {
318  return "Wrong array length of demands variables";
319  }
320  return "";
321 }
322 
323 std::string ValidateIntModConstraint(const CpModelProto& model,
324  const ConstraintProto& ct) {
325  if (ct.int_mod().vars().size() != 2) {
326  return absl::StrCat("An int_mod constraint should have exactly 2 terms: ",
328  }
329  const IntegerVariableProto& mod_proto = model.variables(ct.int_mod().vars(1));
330  if (mod_proto.domain(0) <= 0) {
331  return absl::StrCat(
332  "An int_mod must have a strictly positive modulo argument: ",
334  }
335  return "";
336 }
337 
338 std::string ValidateIntDivConstraint(const CpModelProto& model,
339  const ConstraintProto& ct) {
340  if (ct.int_div().vars().size() != 2) {
341  return absl::StrCat("An int_div constraint should have exactly 2 terms: ",
343  }
344  return "";
345 }
346 
347 std::string ValidateObjective(const CpModelProto& model,
348  const CpObjectiveProto& obj) {
349  if (!DomainInProtoIsValid(obj)) {
350  return absl::StrCat("The objective has and invalid domain() format: ",
352  }
353  if (obj.vars().size() != obj.coeffs().size()) {
354  return absl::StrCat("vars and coeffs size do not match in objective: ",
356  }
357  for (const int v : obj.vars()) {
358  if (!VariableReferenceIsValid(model, v)) {
359  return absl::StrCat("Out of bound integer variable ", v,
360  " in objective: ", ProtobufShortDebugString(obj));
361  }
362  }
363  if (PossibleIntegerOverflow(model, obj)) {
364  return "Possible integer overflow in objective: " +
365  ProtobufDebugString(obj);
366  }
367  return "";
368 }
369 
370 std::string ValidateSearchStrategies(const CpModelProto& model) {
371  for (const DecisionStrategyProto& strategy : model.search_strategy()) {
372  for (const int ref : strategy.variables()) {
373  if (!VariableReferenceIsValid(model, ref)) {
374  return absl::StrCat("Invalid variable reference in strategy: ",
375  ProtobufShortDebugString(strategy));
376  }
377  }
378  for (const auto& transformation : strategy.transformations()) {
379  if (transformation.positive_coeff() <= 0) {
380  return absl::StrCat("Affine transformation coeff should be positive: ",
381  ProtobufShortDebugString(transformation));
382  }
383  if (!VariableReferenceIsValid(model, transformation.var())) {
384  return absl::StrCat(
385  "Invalid variable reference in affine transformation: ",
386  ProtobufShortDebugString(transformation));
387  }
388  }
389  }
390  return "";
391 }
392 
393 std::string ValidateSolutionHint(const CpModelProto& model) {
394  if (!model.has_solution_hint()) return "";
395  const auto& hint = model.solution_hint();
396  if (hint.vars().size() != hint.values().size()) {
397  return "Invalid solution hint: vars and values do not have the same size.";
398  }
399  for (const int ref : hint.vars()) {
400  if (!VariableReferenceIsValid(model, ref)) {
401  return absl::StrCat("Invalid variable reference in solution hint: ", ref);
402  }
403  }
404  return "";
405 }
406 
407 } // namespace
408 
409 std::string ValidateCpModel(const CpModelProto& model) {
410  for (int v = 0; v < model.variables_size(); ++v) {
411  RETURN_IF_NOT_EMPTY(ValidateIntegerVariable(model, v));
412  }
413  for (int c = 0; c < model.constraints_size(); ++c) {
414  RETURN_IF_NOT_EMPTY(ValidateArgumentReferencesInConstraint(model, c));
415 
416  // By default, a constraint does not support enforcement literals except if
417  // explicitly stated by setting this to true below.
418  bool support_enforcement = false;
419 
420  // Other non-generic validations.
421  // TODO(user): validate all constraints.
422  const ConstraintProto& ct = model.constraints(c);
423  const ConstraintProto::ConstraintCase type = ct.constraint_case();
424  switch (type) {
425  case ConstraintProto::ConstraintCase::kIntDiv:
426  RETURN_IF_NOT_EMPTY(ValidateIntDivConstraint(model, ct));
427  break;
428  case ConstraintProto::ConstraintCase::kIntMod:
429  RETURN_IF_NOT_EMPTY(ValidateIntModConstraint(model, ct));
430  break;
431  case ConstraintProto::ConstraintCase::kTable:
432  RETURN_IF_NOT_EMPTY(ValidateTableConstraint(model, ct));
433  break;
434  case ConstraintProto::ConstraintCase::kBoolOr:
435  support_enforcement = true;
436  break;
437  case ConstraintProto::ConstraintCase::kBoolAnd:
438  support_enforcement = true;
439  break;
440  case ConstraintProto::ConstraintCase::kLinear:
441  support_enforcement = true;
442  if (!DomainInProtoIsValid(ct.linear())) {
443  return absl::StrCat("Invalid domain in constraint #", c, " : ",
445  }
446  if (ct.linear().coeffs_size() != ct.linear().vars_size()) {
447  return absl::StrCat("coeffs_size() != vars_size() in constraint #", c,
448  " : ", ProtobufShortDebugString(ct));
449  }
450  RETURN_IF_NOT_EMPTY(ValidateLinearConstraint(model, ct));
451  break;
452  case ConstraintProto::ConstraintCase::kLinMax: {
453  const std::string target_error =
454  ValidateLinearExpression(model, ct.lin_max().target());
455  if (!target_error.empty()) return target_error;
456  for (int i = 0; i < ct.lin_max().exprs_size(); ++i) {
457  const std::string expr_error =
458  ValidateLinearExpression(model, ct.lin_max().exprs(i));
459  if (!expr_error.empty()) return expr_error;
460  }
461  break;
462  }
463  case ConstraintProto::ConstraintCase::kLinMin: {
464  const std::string target_error =
465  ValidateLinearExpression(model, ct.lin_min().target());
466  if (!target_error.empty()) return target_error;
467  for (int i = 0; i < ct.lin_min().exprs_size(); ++i) {
468  const std::string expr_error =
469  ValidateLinearExpression(model, ct.lin_min().exprs(i));
470  if (!expr_error.empty()) return expr_error;
471  }
472  break;
473  }
474 
475  case ConstraintProto::ConstraintCase::kInterval:
476  support_enforcement = true;
477  RETURN_IF_NOT_EMPTY(ValidateIntervalConstraint(model, ct));
478  break;
479  case ConstraintProto::ConstraintCase::kCumulative:
480  if (ct.cumulative().intervals_size() !=
481  ct.cumulative().demands_size()) {
482  return absl::StrCat(
483  "intervals_size() != demands_size() in constraint #", c, " : ",
485  }
486  break;
487  case ConstraintProto::ConstraintCase::kInverse:
488  if (ct.inverse().f_direct().size() != ct.inverse().f_inverse().size()) {
489  return absl::StrCat("Non-matching fields size in inverse: ",
491  }
492  break;
493  case ConstraintProto::ConstraintCase::kAutomaton:
494  RETURN_IF_NOT_EMPTY(ValidateAutomatonConstraint(model, ct));
495  break;
496  case ConstraintProto::ConstraintCase::kCircuit:
497  RETURN_IF_NOT_EMPTY(ValidateCircuitConstraint(model, ct));
498  break;
499  case ConstraintProto::ConstraintCase::kRoutes:
500  RETURN_IF_NOT_EMPTY(ValidateRoutesConstraint(model, ct));
501  break;
502  case ConstraintProto::ConstraintCase::kNoOverlap2D:
503  RETURN_IF_NOT_EMPTY(ValidateNoOverlap2DConstraint(model, ct));
504  break;
505  case ConstraintProto::ConstraintCase::kReservoir:
506  RETURN_IF_NOT_EMPTY(ValidateReservoirConstraint(model, ct));
507  break;
508  default:
509  break;
510  }
511 
512  // Because some client set fixed enforcement literal which are supported
513  // in the presolve for all constraints, we just check that there is no
514  // non-fixed enforcement.
515  if (!support_enforcement && !ct.enforcement_literal().empty()) {
516  for (const int ref : ct.enforcement_literal()) {
517  const int var = PositiveRef(ref);
518  const Domain domain = ReadDomainFromProto(model.variables(var));
519  if (domain.Size() != 1) {
520  return absl::StrCat(
521  "Enforcement literal not supported in constraint: ",
523  }
524  }
525  }
526  }
527  if (model.has_objective()) {
528  RETURN_IF_NOT_EMPTY(ValidateObjective(model, model.objective()));
529  }
530  RETURN_IF_NOT_EMPTY(ValidateSearchStrategies(model));
531  RETURN_IF_NOT_EMPTY(ValidateSolutionHint(model));
532  for (const int ref : model.assumptions()) {
533  if (!LiteralReferenceIsValid(model, ref)) {
534  return absl::StrCat("Invalid literal reference ", ref,
535  " in the 'assumptions' field.");
536  }
537  }
538  return "";
539 }
540 
541 #undef RETURN_IF_NOT_EMPTY
542 
543 // =============================================================================
544 // Solution Feasibility.
545 // =============================================================================
546 
547 namespace {
548 
549 class ConstraintChecker {
550  public:
551  explicit ConstraintChecker(const std::vector<int64>& variable_values)
552  : variable_values_(variable_values) {}
553 
554  bool LiteralIsTrue(int l) const {
555  if (l >= 0) return variable_values_[l] != 0;
556  return variable_values_[-l - 1] == 0;
557  }
558 
559  bool LiteralIsFalse(int l) const { return !LiteralIsTrue(l); }
560 
561  int64 Value(int var) const {
562  if (var >= 0) return variable_values_[var];
563  return -variable_values_[-var - 1];
564  }
565 
566  bool ConstraintIsEnforced(const ConstraintProto& ct) {
567  for (const int lit : ct.enforcement_literal()) {
568  if (LiteralIsFalse(lit)) return false;
569  }
570  return true;
571  }
572 
573  bool BoolOrConstraintIsFeasible(const ConstraintProto& ct) {
574  for (const int lit : ct.bool_or().literals()) {
575  if (LiteralIsTrue(lit)) return true;
576  }
577  return false;
578  }
579 
580  bool BoolAndConstraintIsFeasible(const ConstraintProto& ct) {
581  for (const int lit : ct.bool_and().literals()) {
582  if (LiteralIsFalse(lit)) return false;
583  }
584  return true;
585  }
586 
587  bool AtMostOneConstraintIsFeasible(const ConstraintProto& ct) {
588  int num_true_literals = 0;
589  for (const int lit : ct.at_most_one().literals()) {
590  if (LiteralIsTrue(lit)) ++num_true_literals;
591  }
592  return num_true_literals <= 1;
593  }
594 
595  bool BoolXorConstraintIsFeasible(const ConstraintProto& ct) {
596  int sum = 0;
597  for (const int lit : ct.bool_xor().literals()) {
598  sum ^= LiteralIsTrue(lit) ? 1 : 0;
599  }
600  return sum == 1;
601  }
602 
603  bool LinearConstraintIsFeasible(const ConstraintProto& ct) {
604  int64 sum = 0;
605  const int num_variables = ct.linear().coeffs_size();
606  for (int i = 0; i < num_variables; ++i) {
607  sum += Value(ct.linear().vars(i)) * ct.linear().coeffs(i);
608  }
609  return DomainInProtoContains(ct.linear(), sum);
610  }
611 
612  bool IntMaxConstraintIsFeasible(const ConstraintProto& ct) {
613  const int64 max = Value(ct.int_max().target());
614  int64 actual_max = kint64min;
615  for (int i = 0; i < ct.int_max().vars_size(); ++i) {
616  actual_max = std::max(actual_max, Value(ct.int_max().vars(i)));
617  }
618  return max == actual_max;
619  }
620 
621  int64 LinearExpressionValue(const LinearExpressionProto& expr) {
622  int64 sum = expr.offset();
623  const int num_variables = expr.vars_size();
624  for (int i = 0; i < num_variables; ++i) {
625  sum += Value(expr.vars(i)) * expr.coeffs(i);
626  }
627  return sum;
628  }
629 
630  bool LinMaxConstraintIsFeasible(const ConstraintProto& ct) {
631  const int64 max = LinearExpressionValue(ct.lin_max().target());
632  int64 actual_max = kint64min;
633  for (int i = 0; i < ct.lin_max().exprs_size(); ++i) {
634  const int64 expr_value = LinearExpressionValue(ct.lin_max().exprs(i));
635  actual_max = std::max(actual_max, expr_value);
636  }
637  return max == actual_max;
638  }
639 
640  bool IntProdConstraintIsFeasible(const ConstraintProto& ct) {
641  const int64 prod = Value(ct.int_prod().target());
642  int64 actual_prod = 1;
643  for (int i = 0; i < ct.int_prod().vars_size(); ++i) {
644  actual_prod *= Value(ct.int_prod().vars(i));
645  }
646  return prod == actual_prod;
647  }
648 
649  bool IntDivConstraintIsFeasible(const ConstraintProto& ct) {
650  return Value(ct.int_div().target()) ==
651  Value(ct.int_div().vars(0)) / Value(ct.int_div().vars(1));
652  }
653 
654  bool IntModConstraintIsFeasible(const ConstraintProto& ct) {
655  return Value(ct.int_mod().target()) ==
656  Value(ct.int_mod().vars(0)) % Value(ct.int_mod().vars(1));
657  }
658 
659  bool IntMinConstraintIsFeasible(const ConstraintProto& ct) {
660  const int64 min = Value(ct.int_min().target());
661  int64 actual_min = kint64max;
662  for (int i = 0; i < ct.int_min().vars_size(); ++i) {
663  actual_min = std::min(actual_min, Value(ct.int_min().vars(i)));
664  }
665  return min == actual_min;
666  }
667 
668  bool LinMinConstraintIsFeasible(const ConstraintProto& ct) {
669  const int64 min = LinearExpressionValue(ct.lin_min().target());
670  int64 actual_min = kint64max;
671  for (int i = 0; i < ct.lin_min().exprs_size(); ++i) {
672  const int64 expr_value = LinearExpressionValue(ct.lin_min().exprs(i));
673  actual_min = std::min(actual_min, expr_value);
674  }
675  return min == actual_min;
676  }
677 
678  bool AllDiffConstraintIsFeasible(const ConstraintProto& ct) {
679  absl::flat_hash_set<int64> values;
680  for (const int v : ct.all_diff().vars()) {
681  if (gtl::ContainsKey(values, Value(v))) return false;
682  values.insert(Value(v));
683  }
684  return true;
685  }
686 
687  bool IntervalConstraintIsFeasible(const ConstraintProto& ct) {
688  const int64 size = Value(ct.interval().size());
689  if (size < 0) return false;
690  return Value(ct.interval().start()) + size == Value(ct.interval().end());
691  }
692 
693  bool NoOverlapConstraintIsFeasible(const CpModelProto& model,
694  const ConstraintProto& ct) {
695  std::vector<std::pair<int64, int64>> start_durations_pairs;
696  for (const int i : ct.no_overlap().intervals()) {
697  const ConstraintProto& interval_constraint = model.constraints(i);
698  if (ConstraintIsEnforced(interval_constraint)) {
699  const IntervalConstraintProto& interval =
700  interval_constraint.interval();
701  start_durations_pairs.push_back(
702  {Value(interval.start()), Value(interval.size())});
703  }
704  }
705  std::sort(start_durations_pairs.begin(), start_durations_pairs.end());
706  int64 previous_end = kint64min;
707  for (const auto pair : start_durations_pairs) {
708  if (pair.first < previous_end) return false;
709  previous_end = pair.first + pair.second;
710  }
711  return true;
712  }
713 
714  bool IntervalsAreDisjoint(const IntervalConstraintProto& interval1,
715  const IntervalConstraintProto& interval2) {
716  return Value(interval1.end()) <= Value(interval2.start()) ||
717  Value(interval2.end()) <= Value(interval1.start());
718  }
719 
720  bool IntervalIsEmpty(const IntervalConstraintProto& interval) {
721  return Value(interval.start()) == Value(interval.end());
722  }
723 
724  bool NoOverlap2DConstraintIsFeasible(const CpModelProto& model,
725  const ConstraintProto& ct) {
726  const auto& arg = ct.no_overlap_2d();
727  // Those intervals from arg.x_intervals and arg.y_intervals where both
728  // the x and y intervals are enforced.
729  std::vector<std::pair<const IntervalConstraintProto* const,
730  const IntervalConstraintProto* const>>
731  enforced_intervals_xy;
732  {
733  const int num_intervals = arg.x_intervals_size();
734  CHECK_EQ(arg.y_intervals_size(), num_intervals);
735  for (int i = 0; i < num_intervals; ++i) {
736  const ConstraintProto& x = model.constraints(arg.x_intervals(i));
737  const ConstraintProto& y = model.constraints(arg.y_intervals(i));
738  if (ConstraintIsEnforced(x) && ConstraintIsEnforced(y) &&
739  (!arg.boxes_with_null_area_can_overlap() ||
740  (!IntervalIsEmpty(x.interval()) &&
741  !IntervalIsEmpty(y.interval())))) {
742  enforced_intervals_xy.push_back({&x.interval(), &y.interval()});
743  }
744  }
745  }
746  const int num_enforced_intervals = enforced_intervals_xy.size();
747  for (int i = 0; i < num_enforced_intervals; ++i) {
748  for (int j = i + 1; j < num_enforced_intervals; ++j) {
749  const auto& xi = *enforced_intervals_xy[i].first;
750  const auto& yi = *enforced_intervals_xy[i].second;
751  const auto& xj = *enforced_intervals_xy[j].first;
752  const auto& yj = *enforced_intervals_xy[j].second;
753  if (!IntervalsAreDisjoint(xi, xj) && !IntervalsAreDisjoint(yi, yj) &&
754  !IntervalIsEmpty(xi) && !IntervalIsEmpty(xj) &&
755  !IntervalIsEmpty(yi) && !IntervalIsEmpty(yj)) {
756  VLOG(1) << "Interval " << i << "(x=[" << Value(xi.start()) << ", "
757  << Value(xi.end()) << "], y=[" << Value(yi.start()) << ", "
758  << Value(yi.end()) << "]) and " << j << "("
759  << "(x=[" << Value(xj.start()) << ", " << Value(xj.end())
760  << "], y=[" << Value(yj.start()) << ", " << Value(yj.end())
761  << "]) are not disjoint.";
762  return false;
763  }
764  }
765  }
766  return true;
767  }
768 
769  bool CumulativeConstraintIsFeasible(const CpModelProto& model,
770  const ConstraintProto& ct) {
771  // TODO(user,user): Improve complexity for large durations.
772  const int64 capacity = Value(ct.cumulative().capacity());
773  const int num_intervals = ct.cumulative().intervals_size();
774  absl::flat_hash_map<int64, int64> usage;
775  for (int i = 0; i < num_intervals; ++i) {
776  const ConstraintProto& interval_constraint =
777  model.constraints(ct.cumulative().intervals(i));
778  if (ConstraintIsEnforced(interval_constraint)) {
779  const IntervalConstraintProto& interval =
780  interval_constraint.interval();
781  const int64 start = Value(interval.start());
782  const int64 duration = Value(interval.size());
783  const int64 demand = Value(ct.cumulative().demands(i));
784  for (int64 t = start; t < start + duration; ++t) {
785  usage[t] += demand;
786  if (usage[t] > capacity) return false;
787  }
788  }
789  }
790  return true;
791  }
792 
793  bool ElementConstraintIsFeasible(const ConstraintProto& ct) {
794  const int index = Value(ct.element().index());
795  return Value(ct.element().vars(index)) == Value(ct.element().target());
796  }
797 
798  bool TableConstraintIsFeasible(const ConstraintProto& ct) {
799  const int size = ct.table().vars_size();
800  if (size == 0) return true;
801  for (int row_start = 0; row_start < ct.table().values_size();
802  row_start += size) {
803  int i = 0;
804  while (Value(ct.table().vars(i)) == ct.table().values(row_start + i)) {
805  ++i;
806  if (i == size) return !ct.table().negated();
807  }
808  }
809  return ct.table().negated();
810  }
811 
812  bool AutomatonConstraintIsFeasible(const ConstraintProto& ct) {
813  // Build the transition table {tail, label} -> head.
814  absl::flat_hash_map<std::pair<int64, int64>, int64> transition_map;
815  const int num_transitions = ct.automaton().transition_tail().size();
816  for (int i = 0; i < num_transitions; ++i) {
817  transition_map[{ct.automaton().transition_tail(i),
818  ct.automaton().transition_label(i)}] =
819  ct.automaton().transition_head(i);
820  }
821 
822  // Walk the automaton.
823  int64 current_state = ct.automaton().starting_state();
824  const int num_steps = ct.automaton().vars_size();
825  for (int i = 0; i < num_steps; ++i) {
826  const std::pair<int64, int64> key = {current_state,
827  Value(ct.automaton().vars(i))};
828  if (!gtl::ContainsKey(transition_map, key)) {
829  return false;
830  }
831  current_state = transition_map[key];
832  }
833 
834  // Check we are now in a final state.
835  for (const int64 final : ct.automaton().final_states()) {
836  if (current_state == final) return true;
837  }
838  return false;
839  }
840 
841  bool CircuitConstraintIsFeasible(const ConstraintProto& ct) {
842  // Compute the set of relevant nodes for the constraint and set the next of
843  // each of them. This also detects duplicate nexts.
844  const int num_arcs = ct.circuit().tails_size();
845  absl::flat_hash_set<int> nodes;
846  absl::flat_hash_map<int, int> nexts;
847  for (int i = 0; i < num_arcs; ++i) {
848  const int tail = ct.circuit().tails(i);
849  const int head = ct.circuit().heads(i);
850  nodes.insert(tail);
851  nodes.insert(head);
852  if (LiteralIsFalse(ct.circuit().literals(i))) continue;
853  if (nexts.contains(tail)) return false; // Duplicate.
854  nexts[tail] = head;
855  }
856 
857  // All node must have a next.
858  int in_cycle;
859  int cycle_size = 0;
860  for (const int node : nodes) {
861  if (!nexts.contains(node)) return false; // No next.
862  if (nexts[node] == node) continue; // skip self-loop.
863  in_cycle = node;
864  ++cycle_size;
865  }
866  if (cycle_size == 0) return true;
867 
868  // Check that we have only one cycle. visited is used to not loop forever if
869  // we have a "rho" shape instead of a cycle.
870  absl::flat_hash_set<int> visited;
871  int current = in_cycle;
872  int num_visited = 0;
873  while (!visited.contains(current)) {
874  ++num_visited;
875  visited.insert(current);
876  current = nexts[current];
877  }
878  if (current != in_cycle) return false; // Rho shape.
879  return num_visited == cycle_size; // Another cycle somewhere if false.
880  }
881 
882  bool RoutesConstraintIsFeasible(const ConstraintProto& ct) {
883  const int num_arcs = ct.routes().tails_size();
884  int num_used_arcs = 0;
885  int num_self_arcs = 0;
886  int num_nodes = 0;
887  std::vector<int> tail_to_head;
888  std::vector<int> depot_nexts;
889  for (int i = 0; i < num_arcs; ++i) {
890  const int tail = ct.routes().tails(i);
891  const int head = ct.routes().heads(i);
892  num_nodes = std::max(num_nodes, 1 + tail);
893  num_nodes = std::max(num_nodes, 1 + head);
894  tail_to_head.resize(num_nodes, -1);
895  if (LiteralIsTrue(ct.routes().literals(i))) {
896  if (tail == head) {
897  if (tail == 0) return false;
898  ++num_self_arcs;
899  continue;
900  }
901  ++num_used_arcs;
902  if (tail == 0) {
903  depot_nexts.push_back(head);
904  } else {
905  if (tail_to_head[tail] != -1) return false;
906  tail_to_head[tail] = head;
907  }
908  }
909  }
910 
911  // An empty constraint with no node to visit should be feasible.
912  if (num_nodes == 0) return true;
913 
914  // Make sure each routes from the depot go back to it, and count such arcs.
915  int count = 0;
916  for (int start : depot_nexts) {
917  ++count;
918  while (start != 0) {
919  if (tail_to_head[start] == -1) return false;
920  start = tail_to_head[start];
921  ++count;
922  }
923  }
924 
925  if (count != num_used_arcs) {
926  VLOG(1) << "count: " << count << " != num_used_arcs:" << num_used_arcs;
927  return false;
928  }
929 
930  // Each routes cover as many node as there is arcs, but this way we count
931  // multiple times the depot. So the number of nodes covered are:
932  // count - depot_nexts.size() + 1.
933  // And this number + the self arcs should be num_nodes.
934  if (count - depot_nexts.size() + 1 + num_self_arcs != num_nodes) {
935  VLOG(1) << "Not all nodes are covered!";
936  return false;
937  }
938 
939  return true;
940  }
941 
942  bool InverseConstraintIsFeasible(const ConstraintProto& ct) {
943  const int num_variables = ct.inverse().f_direct_size();
944  if (num_variables != ct.inverse().f_inverse_size()) return false;
945  // Check that f_inverse(f_direct(i)) == i; this is sufficient.
946  for (int i = 0; i < num_variables; i++) {
947  const int fi = Value(ct.inverse().f_direct(i));
948  if (fi < 0 || num_variables <= fi) return false;
949  if (i != Value(ct.inverse().f_inverse(fi))) return false;
950  }
951  return true;
952  }
953 
954  bool ReservoirConstraintIsFeasible(const ConstraintProto& ct) {
955  const int num_variables = ct.reservoir().times_size();
956  const int64 min_level = ct.reservoir().min_level();
957  const int64 max_level = ct.reservoir().max_level();
958  std::map<int64, int64> deltas;
959  deltas[0] = 0;
960  const bool has_active_variables = ct.reservoir().actives_size() > 0;
961  for (int i = 0; i < num_variables; i++) {
962  const int64 time = Value(ct.reservoir().times(i));
963  if (time < 0) {
964  VLOG(1) << "reservoir times(" << i << ") is negative.";
965  return false;
966  }
967  if (!has_active_variables || Value(ct.reservoir().actives(i)) == 1) {
968  deltas[time] += ct.reservoir().demands(i);
969  }
970  }
971  int64 current_level = 0;
972  for (const auto& delta : deltas) {
973  current_level += delta.second;
974  if (current_level < min_level || current_level > max_level) {
975  VLOG(1) << "Reservoir level " << current_level
976  << " is out of bounds at time" << delta.first;
977  return false;
978  }
979  }
980  return true;
981  }
982 
983  private:
984  std::vector<int64> variable_values_;
985 };
986 
987 } // namespace
988 
989 bool SolutionIsFeasible(const CpModelProto& model,
990  const std::vector<int64>& variable_values,
991  const CpModelProto* mapping_proto,
992  const std::vector<int>* postsolve_mapping) {
993  if (variable_values.size() != model.variables_size()) {
994  VLOG(1) << "Wrong number of variables in the solution vector";
995  return false;
996  }
997 
998  // Check that all values fall in the variable domains.
999  for (int i = 0; i < model.variables_size(); ++i) {
1000  if (!DomainInProtoContains(model.variables(i), variable_values[i])) {
1001  VLOG(1) << "Variable #" << i << " has value " << variable_values[i]
1002  << " which do not fall in its domain: "
1003  << ProtobufShortDebugString(model.variables(i));
1004  return false;
1005  }
1006  }
1007 
1008  CHECK_EQ(variable_values.size(), model.variables_size());
1009  ConstraintChecker checker(variable_values);
1010 
1011  for (int c = 0; c < model.constraints_size(); ++c) {
1012  const ConstraintProto& ct = model.constraints(c);
1013 
1014  if (!checker.ConstraintIsEnforced(ct)) continue;
1015 
1016  bool is_feasible = true;
1017  const ConstraintProto::ConstraintCase type = ct.constraint_case();
1018  switch (type) {
1019  case ConstraintProto::ConstraintCase::kBoolOr:
1020  is_feasible = checker.BoolOrConstraintIsFeasible(ct);
1021  break;
1022  case ConstraintProto::ConstraintCase::kBoolAnd:
1023  is_feasible = checker.BoolAndConstraintIsFeasible(ct);
1024  break;
1025  case ConstraintProto::ConstraintCase::kAtMostOne:
1026  is_feasible = checker.AtMostOneConstraintIsFeasible(ct);
1027  break;
1028  case ConstraintProto::ConstraintCase::kBoolXor:
1029  is_feasible = checker.BoolXorConstraintIsFeasible(ct);
1030  break;
1031  case ConstraintProto::ConstraintCase::kLinear:
1032  is_feasible = checker.LinearConstraintIsFeasible(ct);
1033  break;
1034  case ConstraintProto::ConstraintCase::kIntProd:
1035  is_feasible = checker.IntProdConstraintIsFeasible(ct);
1036  break;
1037  case ConstraintProto::ConstraintCase::kIntDiv:
1038  is_feasible = checker.IntDivConstraintIsFeasible(ct);
1039  break;
1040  case ConstraintProto::ConstraintCase::kIntMod:
1041  is_feasible = checker.IntModConstraintIsFeasible(ct);
1042  break;
1043  case ConstraintProto::ConstraintCase::kIntMin:
1044  is_feasible = checker.IntMinConstraintIsFeasible(ct);
1045  break;
1046  case ConstraintProto::ConstraintCase::kLinMin:
1047  is_feasible = checker.LinMinConstraintIsFeasible(ct);
1048  break;
1049  case ConstraintProto::ConstraintCase::kIntMax:
1050  is_feasible = checker.IntMaxConstraintIsFeasible(ct);
1051  break;
1052  case ConstraintProto::ConstraintCase::kLinMax:
1053  is_feasible = checker.LinMaxConstraintIsFeasible(ct);
1054  break;
1055  case ConstraintProto::ConstraintCase::kAllDiff:
1056  is_feasible = checker.AllDiffConstraintIsFeasible(ct);
1057  break;
1058  case ConstraintProto::ConstraintCase::kInterval:
1059  is_feasible = checker.IntervalConstraintIsFeasible(ct);
1060  break;
1061  case ConstraintProto::ConstraintCase::kNoOverlap:
1062  is_feasible = checker.NoOverlapConstraintIsFeasible(model, ct);
1063  break;
1064  case ConstraintProto::ConstraintCase::kNoOverlap2D:
1065  is_feasible = checker.NoOverlap2DConstraintIsFeasible(model, ct);
1066  break;
1067  case ConstraintProto::ConstraintCase::kCumulative:
1068  is_feasible = checker.CumulativeConstraintIsFeasible(model, ct);
1069  break;
1070  case ConstraintProto::ConstraintCase::kElement:
1071  is_feasible = checker.ElementConstraintIsFeasible(ct);
1072  break;
1073  case ConstraintProto::ConstraintCase::kTable:
1074  is_feasible = checker.TableConstraintIsFeasible(ct);
1075  break;
1076  case ConstraintProto::ConstraintCase::kAutomaton:
1077  is_feasible = checker.AutomatonConstraintIsFeasible(ct);
1078  break;
1079  case ConstraintProto::ConstraintCase::kCircuit:
1080  is_feasible = checker.CircuitConstraintIsFeasible(ct);
1081  break;
1082  case ConstraintProto::ConstraintCase::kRoutes:
1083  is_feasible = checker.RoutesConstraintIsFeasible(ct);
1084  break;
1085  case ConstraintProto::ConstraintCase::kInverse:
1086  is_feasible = checker.InverseConstraintIsFeasible(ct);
1087  break;
1088  case ConstraintProto::ConstraintCase::kReservoir:
1089  is_feasible = checker.ReservoirConstraintIsFeasible(ct);
1090  break;
1091  case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1092  // Empty constraint is always feasible.
1093  break;
1094  default:
1095  LOG(FATAL) << "Unuspported constraint: " << ConstraintCaseName(type);
1096  }
1097 
1098  // Display a message to help debugging.
1099  if (!is_feasible) {
1100  VLOG(1) << "Failing constraint #" << c << " : "
1101  << ProtobufShortDebugString(model.constraints(c));
1102  if (mapping_proto != nullptr && postsolve_mapping != nullptr) {
1103  std::vector<int> reverse_map(mapping_proto->variables().size(), -1);
1104  for (int var = 0; var < postsolve_mapping->size(); ++var) {
1105  reverse_map[(*postsolve_mapping)[var]] = var;
1106  }
1107  for (const int var : UsedVariables(model.constraints(c))) {
1108  VLOG(1) << "var: " << var << " mapped_to: " << reverse_map[var]
1109  << " value: " << variable_values[var] << " initial_domain: "
1110  << ReadDomainFromProto(model.variables(var))
1111  << " postsolved_domain: "
1112  << ReadDomainFromProto(mapping_proto->variables(var));
1113  }
1114  } else {
1115  for (const int var : UsedVariables(model.constraints(c))) {
1116  VLOG(1) << "var: " << var << " value: " << variable_values[var];
1117  }
1118  }
1119  return false;
1120  }
1121  }
1122  return true;
1123 }
1124 
1125 } // namespace sat
1126 } // namespace operations_research
var
IntVar * var
Definition: expr_array.cc:1858
tail
int64 tail
Definition: routing_flow.cc:127
min
int64 min
Definition: alldiff_cst.cc:138
map_util.h
VLOG
#define VLOG(verboselevel)
Definition: base/logging.h:978
cp_model.pb.h
max
int64 max
Definition: alldiff_cst.cc:139
bound
int64 bound
Definition: routing_search.cc:971
LOG
#define LOG(severity)
Definition: base/logging.h:420
operations_research::CapProd
int64 CapProd(int64 x, int64 y)
Definition: saturated_arithmetic.h:231
FATAL
const int FATAL
Definition: log_severity.h:32
proto_utils.h
operations_research::sat::UsedVariables
std::vector< int > UsedVariables(const ConstraintProto &ct)
Definition: cp_model_utils.cc:429
logging.h
saturated_arithmetic.h
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::sat::GetReferencesUsedByConstraint
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)
Definition: cp_model_utils.cc:46
kint64min
static const int64 kint64min
Definition: integral_types.h:60
operations_research::Domain
We call domain any subset of Int64 = [kint64min, kint64max].
Definition: sorted_interval_list.h:81
int64
int64_t int64
Definition: integral_types.h:34
operations_research::sat::SolutionIsFeasible
bool SolutionIsFeasible(const CpModelProto &model, const std::vector< int64 > &variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
Definition: cp_model_checker.cc:989
index
int index
Definition: pack.cc:508
operations_research::sat::UsedIntervals
std::vector< int > UsedIntervals(const ConstraintProto &ct)
Definition: cp_model_utils.cc:444
operations_research::ProtobufDebugString
std::string ProtobufDebugString(const P &message)
Definition: port/proto_utils.h:53
demand
int64 demand
Definition: resource.cc:123
operations_research::sat::PositiveRef
int PositiveRef(int ref)
Definition: cp_model_utils.h:33
operations_research::CapAdd
int64 CapAdd(int64 x, int64 y)
Definition: saturated_arithmetic.h:124
CHECK_EQ
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:697
ct
const Constraint * ct
Definition: demon_profiler.cc:42
operations_research::sat::Value
std::function< int64(const Model &)> Value(IntegerVariable v)
Definition: integer.h:1470
operations_research::sat::DomainInProtoContains
bool DomainInProtoContains(const ProtoWithDomain &proto, int64 value)
Definition: cp_model_utils.h:82
operations_research::sat::NegatedRef
int NegatedRef(int ref)
Definition: cp_model_utils.h:32
sorted_interval_list.h
operations_research::IntervalsAreSortedAndNonAdjacent
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
Returns true iff we have:
Definition: sorted_interval_list.cc:37
model
GRBmodel * model
Definition: gurobi_interface.cc:269
operations_research::Domain::Size
int64 Size() const
Returns the number of elements in the domain.
Definition: sorted_interval_list.cc:194
operations_research::sat::RefIsPositive
bool RefIsPositive(int ref)
Definition: cp_model_utils.h:34
RETURN_IF_NOT_EMPTY
#define RETURN_IF_NOT_EMPTY(statement)
Definition: cp_model_checker.cc:41
operations_research::ProtobufShortDebugString
std::string ProtobufShortDebugString(const P &message)
Definition: port/proto_utils.h:58
hash.h
delta
int64 delta
Definition: resource.cc:1684
operations_research::sat::ConstraintCaseName
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
Definition: cp_model_utils.cc:373
capacity
int64 capacity
Definition: routing_flow.cc:129
interval
IntervalVar * interval
Definition: resource.cc:98
proto
CpModelProto proto
Definition: cp_model_fz_solver.cc:107
cp_model_checker.h
head
int64 head
Definition: routing_flow.cc:128
operations_research::sat::ReadDomainFromProto
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Definition: cp_model_utils.h:102
operations_research::sat::ValidateCpModel
std::string ValidateCpModel(const CpModelProto &model)
Definition: cp_model_checker.cc:409
kint64max
static const int64 kint64max
Definition: integral_types.h:62
cp_model_utils.h
time
int64 time
Definition: resource.cc:1683
gtl::ContainsKey
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:170