OR-Tools  9.1
cp_model_checker.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 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 <cstdint>
18 #include <limits>
19 #include <memory>
20 #include <utility>
21 
22 #include "absl/container/flat_hash_map.h"
23 #include "absl/container/flat_hash_set.h"
24 #include "absl/strings/str_cat.h"
25 #include "ortools/base/hash.h"
26 #include "ortools/base/logging.h"
27 #include "ortools/base/map_util.h"
33 
34 namespace operations_research {
35 namespace sat {
36 namespace {
37 
38 // =============================================================================
39 // CpModelProto validation.
40 // =============================================================================
41 
42 // If the string returned by "statement" is not empty, returns it.
43 #define RETURN_IF_NOT_EMPTY(statement) \
44  do { \
45  const std::string error_message = statement; \
46  if (!error_message.empty()) return error_message; \
47  } while (false)
48 
49 template <typename ProtoWithDomain>
50 bool DomainInProtoIsValid(const ProtoWithDomain& proto) {
51  if (proto.domain().size() % 2) return false;
52  std::vector<ClosedInterval> domain;
53  for (int i = 0; i < proto.domain_size(); i += 2) {
54  if (proto.domain(i) > proto.domain(i + 1)) return false;
55  domain.push_back({proto.domain(i), proto.domain(i + 1)});
56  }
57  return IntervalsAreSortedAndNonAdjacent(domain);
58 }
59 
60 bool VariableReferenceIsValid(const CpModelProto& model, int reference) {
61  // We do it this way to avoid overflow if reference is kint64min for instance.
62  if (reference >= model.variables_size()) return false;
63  return reference >= -static_cast<int>(model.variables_size());
64 }
65 
66 bool LiteralReferenceIsValid(const CpModelProto& model, int reference) {
67  if (!VariableReferenceIsValid(model, reference)) return false;
68  const auto& var_proto = model.variables(PositiveRef(reference));
69  const int64_t min_domain = var_proto.domain(0);
70  const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
71  return min_domain >= 0 && max_domain <= 1;
72 }
73 
74 std::string ValidateIntegerVariable(const CpModelProto& model, int v) {
75  const IntegerVariableProto& proto = model.variables(v);
76  if (proto.domain_size() == 0) {
77  return absl::StrCat("var #", v,
78  " has no domain(): ", ProtobufShortDebugString(proto));
79  }
80  if (proto.domain_size() % 2 != 0) {
81  return absl::StrCat("var #", v, " has an odd domain() size: ",
83  }
84  if (!DomainInProtoIsValid(proto)) {
85  return absl::StrCat("var #", v, " has and invalid domain() format: ",
87  }
88 
89  // Internally, we often take the negation of a domain, and we also want to
90  // have sentinel values greater than the min/max of a variable domain, so
91  // the domain must fall in [kint64min + 2, kint64max - 1].
92  const int64_t lb = proto.domain(0);
93  const int64_t ub = proto.domain(proto.domain_size() - 1);
94  if (lb < std::numeric_limits<int64_t>::min() + 2 ||
96  return absl::StrCat(
97  "var #", v, " domain do not fall in [kint64min + 2, kint64max - 1]. ",
99  }
100 
101  // We do compute ub - lb in some place in the code and do not want to deal
102  // with overflow everywhere. This seems like a reasonable precondition anyway.
103  if (lb < 0 && lb + std::numeric_limits<int64_t>::max() < ub) {
104  return absl::StrCat(
105  "var #", v,
106  " has a domain that is too large, i.e. |UB - LB| overflow an int64_t: ",
108  }
109 
110  return "";
111 }
112 
113 std::string ValidateArgumentReferencesInConstraint(const CpModelProto& model,
114  int c) {
115  const ConstraintProto& ct = model.constraints(c);
116  IndexReferences references = GetReferencesUsedByConstraint(ct);
117  for (const int v : references.variables) {
118  if (!VariableReferenceIsValid(model, v)) {
119  return absl::StrCat("Out of bound integer variable ", v,
120  " in constraint #", c, " : ",
122  }
123  }
124  for (const int lit : ct.enforcement_literal()) {
125  if (!LiteralReferenceIsValid(model, lit)) {
126  return absl::StrCat("Invalid enforcement literal ", lit,
127  " in constraint #", c, " : ",
129  }
130  }
131  for (const int lit : references.literals) {
132  if (!LiteralReferenceIsValid(model, lit)) {
133  return absl::StrCat("Invalid literal ", lit, " in constraint #", c, " : ",
135  }
136  }
137  for (const int i : UsedIntervals(ct)) {
138  if (i < 0 || i >= model.constraints_size()) {
139  return absl::StrCat("Out of bound interval ", i, " in constraint #", c,
140  " : ", ProtobufShortDebugString(ct));
141  }
142  if (model.constraints(i).constraint_case() !=
143  ConstraintProto::ConstraintCase::kInterval) {
144  return absl::StrCat(
145  "Interval ", i,
146  " does not refer to an interval constraint. Problematic constraint #",
147  c, " : ", ProtobufShortDebugString(ct));
148  }
149  }
150  return "";
151 }
152 
153 template <class LinearExpressionProto>
154 bool PossibleIntegerOverflow(const CpModelProto& model,
155  const LinearExpressionProto& proto,
156  int64_t offset = 0) {
157  int64_t sum_min = -std::abs(offset);
158  int64_t sum_max = +std::abs(offset);
159  for (int i = 0; i < proto.vars_size(); ++i) {
160  const int ref = proto.vars(i);
161  const auto& var_proto = model.variables(PositiveRef(ref));
162  const int64_t min_domain = var_proto.domain(0);
163  const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
164  if (proto.coeffs(i) == std::numeric_limits<int64_t>::min()) return true;
165  const int64_t coeff =
166  RefIsPositive(ref) ? proto.coeffs(i) : -proto.coeffs(i);
167  const int64_t prod1 = CapProd(min_domain, coeff);
168  const int64_t prod2 = CapProd(max_domain, coeff);
169 
170  // Note that we use min/max with zero to disallow "alternative" terms and
171  // be sure that we cannot have an overflow if we do the computation in a
172  // different order.
173  sum_min = CapAdd(sum_min, std::min(int64_t{0}, std::min(prod1, prod2)));
174  sum_max = CapAdd(sum_max, std::max(int64_t{0}, std::max(prod1, prod2)));
175  for (const int64_t v : {prod1, prod2, sum_min, sum_max}) {
178  return true;
179  }
180  }
181 
182  // In addition to computing the min/max possible sum, we also often compare
183  // it with the constraint bounds, so we do not want max - min to overflow.
184  if (sum_min < 0 && sum_min + std::numeric_limits<int64_t>::max() < sum_max) {
185  return true;
186  }
187  return false;
188 }
189 
190 int64_t MinOfRef(const CpModelProto& model, int ref) {
191  const IntegerVariableProto& var_proto = model.variables(PositiveRef(ref));
192  if (RefIsPositive(ref)) {
193  return var_proto.domain(0);
194  } else {
195  return -var_proto.domain(var_proto.domain_size() - 1);
196  }
197 }
198 
199 int64_t MaxOfRef(const CpModelProto& model, int ref) {
200  const IntegerVariableProto& var_proto = model.variables(PositiveRef(ref));
201  if (RefIsPositive(ref)) {
202  return var_proto.domain(var_proto.domain_size() - 1);
203  } else {
204  return -var_proto.domain(0);
205  }
206 }
207 
208 template <class LinearExpressionProto>
209 int64_t MinOfExpression(const CpModelProto& model,
210  const LinearExpressionProto& proto) {
211  int64_t sum_min = proto.offset();
212  for (int i = 0; i < proto.vars_size(); ++i) {
213  const int ref = proto.vars(i);
214  const int64_t coeff = proto.coeffs(i);
215  sum_min =
216  CapAdd(sum_min, coeff >= 0 ? CapProd(MinOfRef(model, ref), coeff)
217  : CapProd(MaxOfRef(model, ref), coeff));
218  }
219 
220  return sum_min;
221 }
222 
223 template <class LinearExpressionProto>
224 int64_t MaxOfExpression(const CpModelProto& model,
225  const LinearExpressionProto& proto) {
226  int64_t sum_max = proto.offset();
227  for (int i = 0; i < proto.vars_size(); ++i) {
228  const int ref = proto.vars(i);
229  const int64_t coeff = proto.coeffs(i);
230  sum_max =
231  CapAdd(sum_max, coeff >= 0 ? CapProd(MaxOfRef(model, ref), coeff)
232  : CapProd(MinOfRef(model, ref), coeff));
233  }
234 
235  return sum_max;
236 }
237 
238 int64_t IntervalSizeMin(const CpModelProto& model, int interval_index) {
239  DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
240  model.constraints(interval_index).constraint_case());
241  const IntervalConstraintProto& proto =
242  model.constraints(interval_index).interval();
243  if (proto.has_size_view()) {
244  return MinOfExpression(model, proto.size_view());
245  } else {
246  return MinOfRef(model, proto.size());
247  }
248 }
249 
250 int64_t IntervalSizeMax(const CpModelProto& model, int interval_index) {
251  DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
252  model.constraints(interval_index).constraint_case());
253  const IntervalConstraintProto& proto =
254  model.constraints(interval_index).interval();
255  if (proto.has_size_view()) {
256  return MaxOfExpression(model, proto.size_view());
257  } else {
258  return MaxOfRef(model, proto.size());
259  }
260 }
261 
262 Domain DomainOfRef(const CpModelProto& model, int ref) {
263  const Domain domain = ReadDomainFromProto(model.variables(PositiveRef(ref)));
264  return RefIsPositive(ref) ? domain : domain.Negation();
265 }
266 
267 std::string ValidateLinearExpression(const CpModelProto& model,
268  const LinearExpressionProto& expr) {
269  if (expr.coeffs_size() != expr.vars_size()) {
270  return absl::StrCat("coeffs_size() != vars_size() in linear expression: ",
272  }
273  if (PossibleIntegerOverflow(model, expr, expr.offset())) {
274  return absl::StrCat("Possible overflow in linear expression: ",
276  }
277  return "";
278 }
279 
280 std::string ValidateLinearConstraint(const CpModelProto& model,
281  const ConstraintProto& ct) {
282  if (!DomainInProtoIsValid(ct.linear())) {
283  return absl::StrCat("Invalid domain in constraint : ",
285  }
286  if (ct.linear().coeffs_size() != ct.linear().vars_size()) {
287  return absl::StrCat("coeffs_size() != vars_size() in constraint: ",
289  }
290  const LinearConstraintProto& arg = ct.linear();
291  if (PossibleIntegerOverflow(model, arg)) {
292  return "Possible integer overflow in constraint: " +
294  }
295  return "";
296 }
297 
298 std::string ValidateIntModConstraint(const CpModelProto& model,
299  const ConstraintProto& ct) {
300  if (ct.int_mod().vars().size() != 2) {
301  return absl::StrCat("An int_mod constraint should have exactly 2 terms: ",
303  }
304  const int mod_var = ct.int_mod().vars(1);
305  const IntegerVariableProto& mod_proto = model.variables(PositiveRef(mod_var));
306  if ((RefIsPositive(mod_var) && mod_proto.domain(0) <= 0) ||
307  (!RefIsPositive(mod_var) &&
308  mod_proto.domain(mod_proto.domain_size() - 1) >= 0)) {
309  return absl::StrCat(
310  "An int_mod must have a strictly positive modulo argument: ",
312  }
313  return "";
314 }
315 
316 std::string ValidateIntProdConstraint(const CpModelProto& model,
317  const ConstraintProto& ct) {
318  if (ct.int_prod().vars().size() != 2) {
319  return absl::StrCat("An int_prod constraint should have exactly 2 terms: ",
321  }
322 
323  // Detect potential overflow if some of the variables span across 0.
324  const Domain product_domain =
325  ReadDomainFromProto(model.variables(PositiveRef(ct.int_prod().vars(0))))
326  .ContinuousMultiplicationBy(ReadDomainFromProto(
327  model.variables(PositiveRef(ct.int_prod().vars(1)))));
328  if ((product_domain.Max() == std::numeric_limits<int64_t>::max() &&
329  product_domain.Min() < 0) ||
330  (product_domain.Min() == std::numeric_limits<int64_t>::min() &&
331  product_domain.Max() > 0)) {
332  return absl::StrCat("Potential integer overflow in constraint: ",
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  const IntegerVariableProto& divisor_proto =
345  model.variables(PositiveRef(ct.int_div().vars(1)));
346  if (divisor_proto.domain(0) <= 0 &&
347  divisor_proto.domain(divisor_proto.domain_size() - 1) >= 0) {
348  return absl::StrCat("The divisor cannot span across zero in constraint: ",
350  }
351  return "";
352 }
353 
354 std::string ValidateTableConstraint(const CpModelProto& model,
355  const ConstraintProto& ct) {
356  const TableConstraintProto& arg = ct.table();
357  if (arg.vars().empty()) return "";
358  if (arg.values().size() % arg.vars().size() != 0) {
359  return absl::StrCat(
360  "The flat encoding of a table constraint must be a multiple of the "
361  "number of variable: ",
363  }
364  return "";
365 }
366 
367 std::string ValidateAutomatonConstraint(const CpModelProto& model,
368  const ConstraintProto& ct) {
369  const int num_transistions = ct.automaton().transition_tail().size();
370  if (num_transistions != ct.automaton().transition_head().size() ||
371  num_transistions != ct.automaton().transition_label().size()) {
372  return absl::StrCat(
373  "The transitions repeated fields must have the same size: ",
375  }
376  absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> tail_label_to_head;
377  for (int i = 0; i < num_transistions; ++i) {
378  const int64_t tail = ct.automaton().transition_tail(i);
379  const int64_t head = ct.automaton().transition_head(i);
380  const int64_t label = ct.automaton().transition_label(i);
381  const auto [it, inserted] =
382  tail_label_to_head.insert({{tail, label}, head});
383  if (!inserted) {
384  if (it->second == head) {
385  return absl::StrCat("automaton: duplicate transition ", tail, " --(",
386  label, ")--> ", head);
387  } else {
388  return absl::StrCat("automaton: incompatible transitions ", tail,
389  " --(", label, ")--> ", head, " and ", tail, " --(",
390  label, ")--> ", it->second);
391  }
392  }
393  }
394  return "";
395 }
396 
397 template <typename GraphProto>
398 std::string ValidateGraphInput(bool is_route, const CpModelProto& model,
399  const GraphProto& graph) {
400  const int size = graph.tails().size();
401  if (graph.heads().size() != size || graph.literals().size() != size) {
402  return absl::StrCat("Wrong field sizes in graph: ",
403  ProtobufShortDebugString(graph));
404  }
405 
406  // We currently disallow multiple self-loop on the same node.
407  absl::flat_hash_set<int> self_loops;
408  for (int i = 0; i < size; ++i) {
409  if (graph.heads(i) != graph.tails(i)) continue;
410  if (!self_loops.insert(graph.heads(i)).second) {
411  return absl::StrCat(
412  "Circuit/Route constraint contains multiple self-loop involving "
413  "node ",
414  graph.heads(i));
415  }
416  if (is_route && graph.tails(i) == 0) {
417  return absl::StrCat(
418  "A route constraint cannot have a self-loop on the depot (node 0)");
419  }
420  }
421 
422  return "";
423 }
424 
425 std::string ValidateRoutesConstraint(const CpModelProto& model,
426  const ConstraintProto& ct) {
427  int max_node = 0;
428  absl::flat_hash_set<int> nodes;
429  for (const int node : ct.routes().tails()) {
430  if (node < 0) {
431  return "All node in a route constraint must be in [0, num_nodes)";
432  }
433  nodes.insert(node);
434  max_node = std::max(max_node, node);
435  }
436  for (const int node : ct.routes().heads()) {
437  if (node < 0) {
438  return "All node in a route constraint must be in [0, num_nodes)";
439  }
440  nodes.insert(node);
441  max_node = std::max(max_node, node);
442  }
443  if (!nodes.empty() && max_node != nodes.size() - 1) {
444  return absl::StrCat(
445  "All nodes in a route constraint must have incident arcs");
446  }
447 
448  return ValidateGraphInput(/*is_route=*/true, model, ct.routes());
449 }
450 
451 std::string ValidateDomainIsPositive(const CpModelProto& model, int ref,
452  const std::string& ref_name) {
453  if (ref < 0) {
454  const IntegerVariableProto& var_proto = model.variables(NegatedRef(ref));
455  if (var_proto.domain(var_proto.domain_size() - 1) > 0) {
456  return absl::StrCat("Negative value in ", ref_name,
457  " domain: negation of ",
458  ProtobufDebugString(var_proto));
459  }
460  } else {
461  const IntegerVariableProto& var_proto = model.variables(ref);
462  if (var_proto.domain(0) < 0) {
463  return absl::StrCat("Negative value in ", ref_name,
464  " domain: ", ProtobufDebugString(var_proto));
465  }
466  }
467  return "";
468 }
469 
470 void AppendToOverflowValidator(const LinearExpressionProto& input,
471  LinearExpressionProto* output) {
472  output->mutable_vars()->Add(input.vars().begin(), input.vars().end());
473  output->mutable_coeffs()->Add(input.coeffs().begin(), input.coeffs().end());
474 
475  // We add the absolute value to be sure that future computation will not
476  // overflow depending on the order they are performed in.
477  output->set_offset(
478  CapAdd(std::abs(output->offset()), std::abs(input.offset())));
479 }
480 
481 std::string ValidateIntervalConstraint(const CpModelProto& model,
482  const ConstraintProto& ct) {
483  if (ct.enforcement_literal().size() > 1) {
484  return absl::StrCat(
485  "Interval with more than one enforcement literals are currently not "
486  "supported: ",
488  }
489 
490  LinearExpressionProto for_overflow_validation;
491 
492  const IntervalConstraintProto& arg = ct.interval();
493  int num_view = 0;
494  if (arg.has_start_view()) {
495  ++num_view;
496  if (arg.start_view().vars_size() > 1) {
497  return "Interval with a start expression containing more than one "
498  "variable are currently not supported.";
499  }
500  RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, arg.start_view()));
501  AppendToOverflowValidator(arg.start_view(), &for_overflow_validation);
502  } else {
503  for_overflow_validation.add_vars(arg.start());
504  for_overflow_validation.add_coeffs(1);
505  }
506  if (arg.has_size_view()) {
507  ++num_view;
508  if (arg.size_view().vars_size() > 1) {
509  return "Interval with a size expression containing more than one "
510  "variable are currently not supported.";
511  }
512  RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, arg.size_view()));
513  if (ct.enforcement_literal().empty() &&
514  MinOfExpression(model, arg.size_view()) < 0) {
515  return absl::StrCat(
516  "The size of an performed interval must be >= 0 in constraint: ",
518  }
519  AppendToOverflowValidator(arg.size_view(), &for_overflow_validation);
520  } else {
521  if (ct.enforcement_literal().empty()) {
522  const std::string domain_error =
523  ValidateDomainIsPositive(model, arg.size(), "size");
524  if (!domain_error.empty()) {
525  return absl::StrCat(domain_error,
526  " in constraint: ", ProtobufDebugString(ct));
527  }
528  }
529 
530  for_overflow_validation.add_vars(arg.size());
531  for_overflow_validation.add_coeffs(1);
532  }
533  if (arg.has_end_view()) {
534  ++num_view;
535  if (arg.end_view().vars_size() > 1) {
536  return "Interval with a end expression containing more than one "
537  "variable are currently not supported.";
538  }
539  RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, arg.end_view()));
540  AppendToOverflowValidator(arg.end_view(), &for_overflow_validation);
541  } else {
542  for_overflow_validation.add_vars(arg.end());
543  for_overflow_validation.add_coeffs(1);
544  }
545 
546  if (PossibleIntegerOverflow(model, for_overflow_validation,
547  for_overflow_validation.offset())) {
548  return absl::StrCat("Possible overflow in interval: ",
549  ProtobufShortDebugString(ct.interval()));
550  }
551 
552  if (num_view != 0 && num_view != 3) {
553  return absl::StrCat(
554  "Interval must use either the var or the view representation, but not "
555  "both: ",
557  }
558  return "";
559 }
560 
561 std::string ValidateCumulativeConstraint(const CpModelProto& model,
562  const ConstraintProto& ct) {
563  if (ct.cumulative().intervals_size() != ct.cumulative().demands_size()) {
564  return absl::StrCat("intervals_size() != demands_size() in constraint: ",
566  }
567 
568  if (ct.cumulative().energies_size() != 0 &&
569  ct.cumulative().energies_size() != ct.cumulative().intervals_size()) {
570  return absl::StrCat("energies_size() != intervals_size() in constraint: ",
572  }
573 
574  for (const int demand_ref : ct.cumulative().demands()) {
575  const std::string domain_error =
576  ValidateDomainIsPositive(model, demand_ref, "demand");
577  if (!domain_error.empty()) {
578  return absl::StrCat(domain_error,
579  " in constraint: ", ProtobufDebugString(ct));
580  }
581  }
582 
583  int64_t sum_max_demands = 0;
584  for (const int demand_ref : ct.cumulative().demands()) {
585  const int64_t demand_max = MaxOfRef(model, demand_ref);
586  DCHECK_GE(demand_max, 0);
587  sum_max_demands = CapAdd(sum_max_demands, demand_max);
588  if (sum_max_demands == std::numeric_limits<int64_t>::max()) {
589  return "The sum of max demands do not fit on an int64_t in constraint: " +
591  }
592  }
593 
594  int64_t sum_max_energies = 0;
595  for (int i = 0; i < ct.cumulative().intervals_size(); ++i) {
596  const int64_t demand_max = MaxOfRef(model, ct.cumulative().demands(i));
597  const int64_t size_max =
598  IntervalSizeMax(model, ct.cumulative().intervals(i));
599  sum_max_energies = CapAdd(sum_max_energies, CapProd(size_max, demand_max));
600  if (sum_max_energies == std::numeric_limits<int64_t>::max()) {
601  return "The sum of max energies (size * demand) do not fit on an int64_t "
602  "in constraint: " +
604  }
605  }
606 
607  for (int i = 0; i < ct.cumulative().energies_size(); ++i) {
608  const LinearExpressionProto& expr = ct.cumulative().energies(i);
609  const std::string error = ValidateLinearExpression(model, expr);
610  if (!error.empty()) {
611  return absl::StrCat(error, "in energy expression of constraint: ",
613  }
614 
615  // The following check is quite loose, but it will catch gross mistakes like
616  // having an empty expression (= 0) with a non zero demand and non null
617  // interval.
618  const int interval = ct.cumulative().intervals(i);
619  const int64_t size_min = IntervalSizeMin(model, interval);
620  const int64_t size_max = IntervalSizeMax(model, interval);
621  const Domain product =
622  DomainOfRef(model, ct.cumulative().demands(i))
623  .ContinuousMultiplicationBy({size_min, size_max});
624  const int64_t energy_min = MinOfExpression(model, expr);
625  const int64_t energy_max = MaxOfExpression(model, expr);
626  if (product.IntersectionWith({energy_min, energy_max}).IsEmpty()) {
627  return absl::StrCat(
628  "The energy expression (with index ", i,
629  ") is not compatible with the product of the size of the interval, "
630  "and the demand in constraint: ",
632  }
633  }
634 
635  return "";
636 }
637 
638 std::string ValidateNoOverlap2DConstraint(const CpModelProto& model,
639  const ConstraintProto& ct) {
640  const int size_x = ct.no_overlap_2d().x_intervals().size();
641  const int size_y = ct.no_overlap_2d().y_intervals().size();
642  if (size_x != size_y) {
643  return absl::StrCat("The two lists of intervals must have the same size: ",
645  }
646 
647  // Checks if the sum of max areas of each rectangle can overflow.
648  int64_t sum_max_areas = 0;
649  for (int i = 0; i < ct.no_overlap_2d().x_intervals().size(); ++i) {
650  const int64_t max_size_x =
651  IntervalSizeMax(model, ct.no_overlap_2d().x_intervals(i));
652  const int64_t max_size_y =
653  IntervalSizeMax(model, ct.no_overlap_2d().y_intervals(i));
654  sum_max_areas = CapAdd(sum_max_areas, CapProd(max_size_x, max_size_y));
655  if (sum_max_areas == std::numeric_limits<int64_t>::max()) {
656  return "Integer overflow when summing all areas in "
657  "constraint: " +
659  }
660  }
661  return "";
662 }
663 
664 std::string ValidateReservoirConstraint(const CpModelProto& model,
665  const ConstraintProto& ct) {
666  if (ct.enforcement_literal_size() > 0) {
667  return "Reservoir does not support enforcement literals.";
668  }
669  if (ct.reservoir().times().size() != ct.reservoir().demands().size()) {
670  return absl::StrCat("Times and demands fields must be of the same size: ",
672  }
673  if (ct.reservoir().min_level() > 0) {
674  return absl::StrCat(
675  "The min level of a reservoir must be <= 0. Please use fixed events to "
676  "setup initial state: ",
678  }
679  if (ct.reservoir().max_level() < 0) {
680  return absl::StrCat(
681  "The max level of a reservoir must be >= 0. Please use fixed events to "
682  "setup initial state: ",
684  }
685 
686  int64_t sum_abs = 0;
687  for (const int64_t demand : ct.reservoir().demands()) {
688  // We test for min int64_t before the abs().
690  return "Possible integer overflow in constraint: " +
692  }
693  sum_abs = CapAdd(sum_abs, std::abs(demand));
694  if (sum_abs == std::numeric_limits<int64_t>::max()) {
695  return "Possible integer overflow in constraint: " +
697  }
698  }
699  if (ct.reservoir().actives_size() > 0 &&
700  ct.reservoir().actives_size() != ct.reservoir().times_size()) {
701  return "Wrong array length of actives variables";
702  }
703  if (ct.reservoir().demands_size() > 0 &&
704  ct.reservoir().demands_size() != ct.reservoir().times_size()) {
705  return "Wrong array length of demands variables";
706  }
707  return "";
708 }
709 
710 std::string ValidateObjective(const CpModelProto& model,
711  const CpObjectiveProto& obj) {
712  if (!DomainInProtoIsValid(obj)) {
713  return absl::StrCat("The objective has and invalid domain() format: ",
715  }
716  if (obj.vars().size() != obj.coeffs().size()) {
717  return absl::StrCat("vars and coeffs size do not match in objective: ",
719  }
720  for (const int v : obj.vars()) {
721  if (!VariableReferenceIsValid(model, v)) {
722  return absl::StrCat("Out of bound integer variable ", v,
723  " in objective: ", ProtobufShortDebugString(obj));
724  }
725  }
726  if (PossibleIntegerOverflow(model, obj)) {
727  return "Possible integer overflow in objective: " +
728  ProtobufDebugString(obj);
729  }
730  return "";
731 }
732 
733 std::string ValidateSearchStrategies(const CpModelProto& model) {
734  for (const DecisionStrategyProto& strategy : model.search_strategy()) {
735  const int vss = strategy.variable_selection_strategy();
741  return absl::StrCat(
742  "Unknown or unsupported variable_selection_strategy: ", vss);
743  }
744  const int drs = strategy.domain_reduction_strategy();
750  return absl::StrCat("Unknown or unsupported domain_reduction_strategy: ",
751  drs);
752  }
753  for (const int ref : strategy.variables()) {
754  if (!VariableReferenceIsValid(model, ref)) {
755  return absl::StrCat("Invalid variable reference in strategy: ",
756  ProtobufShortDebugString(strategy));
757  }
759  ReadDomainFromProto(model.variables(PositiveRef(ref))).Size() >
760  100000) {
761  return absl::StrCat("Variable #", PositiveRef(ref),
762  " has a domain too large to be used in a"
763  " SELECT_MEDIAN_VALUE value selection strategy");
764  }
765  }
766  int previous_index = -1;
767  for (const auto& transformation : strategy.transformations()) {
768  if (transformation.positive_coeff() <= 0) {
769  return absl::StrCat("Affine transformation coeff should be positive: ",
770  ProtobufShortDebugString(transformation));
771  }
772  if (transformation.index() <= previous_index ||
773  transformation.index() >= strategy.variables_size()) {
774  return absl::StrCat(
775  "Invalid indices (must be sorted and valid) in transformation: ",
776  ProtobufShortDebugString(transformation));
777  }
778  previous_index = transformation.index();
779  }
780  }
781  return "";
782 }
783 
784 std::string ValidateSolutionHint(const CpModelProto& model) {
785  if (!model.has_solution_hint()) return "";
786  const auto& hint = model.solution_hint();
787  if (hint.vars().size() != hint.values().size()) {
788  return "Invalid solution hint: vars and values do not have the same size.";
789  }
790  for (const int ref : hint.vars()) {
791  if (!VariableReferenceIsValid(model, ref)) {
792  return absl::StrCat("Invalid variable reference in solution hint: ", ref);
793  }
794  }
795 
796  // Reject hint with duplicate variables has this is likely a user error.
797  absl::flat_hash_set<int> indices;
798  for (const int var : hint.vars()) {
799  const auto insert = indices.insert(PositiveRef(var));
800  if (!insert.second) {
801  return absl::StrCat(
802  "The solution hint contains duplicate variables like the variable "
803  "with index #",
804  PositiveRef(var));
805  }
806  }
807 
808  return "";
809 }
810 
811 } // namespace
812 
813 std::string ValidateCpModel(const CpModelProto& model) {
814  for (int v = 0; v < model.variables_size(); ++v) {
815  RETURN_IF_NOT_EMPTY(ValidateIntegerVariable(model, v));
816  }
817 
818  // Checks all variable references, and validate intervals before scanning the
819  // rest of the constraints.
820  for (int c = 0; c < model.constraints_size(); ++c) {
821  RETURN_IF_NOT_EMPTY(ValidateArgumentReferencesInConstraint(model, c));
822 
823  const ConstraintProto& ct = model.constraints(c);
824  if (ct.constraint_case() == ConstraintProto::kInterval) {
825  RETURN_IF_NOT_EMPTY(ValidateIntervalConstraint(model, ct));
826  }
827  }
828 
829  for (int c = 0; c < model.constraints_size(); ++c) {
830  // By default, a constraint does not support enforcement literals except if
831  // explicitly stated by setting this to true below.
832  bool support_enforcement = false;
833 
834  // Other non-generic validations.
835  // TODO(user): validate all constraints.
836  const ConstraintProto& ct = model.constraints(c);
837  const ConstraintProto::ConstraintCase type = ct.constraint_case();
838  switch (type) {
839  case ConstraintProto::ConstraintCase::kBoolOr:
840  support_enforcement = true;
841  break;
842  case ConstraintProto::ConstraintCase::kBoolAnd:
843  support_enforcement = true;
844  break;
845  case ConstraintProto::ConstraintCase::kLinear:
846  support_enforcement = true;
847  RETURN_IF_NOT_EMPTY(ValidateLinearConstraint(model, ct));
848  break;
849  case ConstraintProto::ConstraintCase::kLinMax: {
851  ValidateLinearExpression(model, ct.lin_max().target()));
852  for (int i = 0; i < ct.lin_max().exprs_size(); ++i) {
854  ValidateLinearExpression(model, ct.lin_max().exprs(i)));
855  }
856  break;
857  }
858  case ConstraintProto::ConstraintCase::kLinMin: {
860  ValidateLinearExpression(model, ct.lin_min().target()));
861  for (int i = 0; i < ct.lin_min().exprs_size(); ++i) {
863  ValidateLinearExpression(model, ct.lin_min().exprs(i)));
864  }
865  break;
866  }
867  case ConstraintProto::ConstraintCase::kIntProd:
868  RETURN_IF_NOT_EMPTY(ValidateIntProdConstraint(model, ct));
869  break;
870  case ConstraintProto::ConstraintCase::kIntDiv:
871  RETURN_IF_NOT_EMPTY(ValidateIntDivConstraint(model, ct));
872  break;
873  case ConstraintProto::ConstraintCase::kIntMod:
874  RETURN_IF_NOT_EMPTY(ValidateIntModConstraint(model, ct));
875  break;
876  case ConstraintProto::ConstraintCase::kInverse:
877  if (ct.inverse().f_direct().size() != ct.inverse().f_inverse().size()) {
878  return absl::StrCat("Non-matching fields size in inverse: ",
880  }
881  break;
882  case ConstraintProto::ConstraintCase::kTable:
883  RETURN_IF_NOT_EMPTY(ValidateTableConstraint(model, ct));
884  break;
885  case ConstraintProto::ConstraintCase::kAutomaton:
886  RETURN_IF_NOT_EMPTY(ValidateAutomatonConstraint(model, ct));
887  break;
888  case ConstraintProto::ConstraintCase::kCircuit:
890  ValidateGraphInput(/*is_route=*/false, model, ct.circuit()));
891  break;
892  case ConstraintProto::ConstraintCase::kRoutes:
893  RETURN_IF_NOT_EMPTY(ValidateRoutesConstraint(model, ct));
894  break;
895  case ConstraintProto::ConstraintCase::kInterval:
896  support_enforcement = true;
897  break;
898  case ConstraintProto::ConstraintCase::kCumulative:
899  RETURN_IF_NOT_EMPTY(ValidateCumulativeConstraint(model, ct));
900  break;
901  case ConstraintProto::ConstraintCase::kNoOverlap2D:
902  RETURN_IF_NOT_EMPTY(ValidateNoOverlap2DConstraint(model, ct));
903  break;
904  case ConstraintProto::ConstraintCase::kReservoir:
905  RETURN_IF_NOT_EMPTY(ValidateReservoirConstraint(model, ct));
906  break;
907  case ConstraintProto::ConstraintCase::kDummyConstraint:
908  return "The dummy constraint should never appear in a model.";
909  default:
910  break;
911  }
912 
913  // Because some client set fixed enforcement literal which are supported
914  // in the presolve for all constraints, we just check that there is no
915  // non-fixed enforcement.
916  if (!support_enforcement && !ct.enforcement_literal().empty()) {
917  for (const int ref : ct.enforcement_literal()) {
918  const int var = PositiveRef(ref);
919  const Domain domain = ReadDomainFromProto(model.variables(var));
920  if (domain.Size() != 1) {
921  return absl::StrCat(
922  "Enforcement literal not supported in constraint: ",
924  }
925  }
926  }
927  }
928  if (model.has_objective()) {
929  RETURN_IF_NOT_EMPTY(ValidateObjective(model, model.objective()));
930  }
931  RETURN_IF_NOT_EMPTY(ValidateSearchStrategies(model));
932  RETURN_IF_NOT_EMPTY(ValidateSolutionHint(model));
933  for (const int ref : model.assumptions()) {
934  if (!LiteralReferenceIsValid(model, ref)) {
935  return absl::StrCat("Invalid literal reference ", ref,
936  " in the 'assumptions' field.");
937  }
938  }
939  return "";
940 }
941 
942 #undef RETURN_IF_NOT_EMPTY
943 
944 // =============================================================================
945 // Solution Feasibility.
946 // =============================================================================
947 
948 namespace {
949 
950 class ConstraintChecker {
951  public:
952  explicit ConstraintChecker(const std::vector<int64_t>& variable_values)
953  : variable_values_(variable_values) {}
954 
955  bool LiteralIsTrue(int l) const {
956  if (l >= 0) return variable_values_[l] != 0;
957  return variable_values_[-l - 1] == 0;
958  }
959 
960  bool LiteralIsFalse(int l) const { return !LiteralIsTrue(l); }
961 
962  int64_t Value(int var) const {
963  if (var >= 0) return variable_values_[var];
964  return -variable_values_[-var - 1];
965  }
966 
967  bool ConstraintIsEnforced(const ConstraintProto& ct) {
968  for (const int lit : ct.enforcement_literal()) {
969  if (LiteralIsFalse(lit)) return false;
970  }
971  return true;
972  }
973 
974  bool BoolOrConstraintIsFeasible(const ConstraintProto& ct) {
975  for (const int lit : ct.bool_or().literals()) {
976  if (LiteralIsTrue(lit)) return true;
977  }
978  return false;
979  }
980 
981  bool BoolAndConstraintIsFeasible(const ConstraintProto& ct) {
982  for (const int lit : ct.bool_and().literals()) {
983  if (LiteralIsFalse(lit)) return false;
984  }
985  return true;
986  }
987 
988  bool AtMostOneConstraintIsFeasible(const ConstraintProto& ct) {
989  int num_true_literals = 0;
990  for (const int lit : ct.at_most_one().literals()) {
991  if (LiteralIsTrue(lit)) ++num_true_literals;
992  }
993  return num_true_literals <= 1;
994  }
995 
996  bool ExactlyOneConstraintIsFeasible(const ConstraintProto& ct) {
997  int num_true_literals = 0;
998  for (const int lit : ct.exactly_one().literals()) {
999  if (LiteralIsTrue(lit)) ++num_true_literals;
1000  }
1001  return num_true_literals == 1;
1002  }
1003 
1004  bool BoolXorConstraintIsFeasible(const ConstraintProto& ct) {
1005  int sum = 0;
1006  for (const int lit : ct.bool_xor().literals()) {
1007  sum ^= LiteralIsTrue(lit) ? 1 : 0;
1008  }
1009  return sum == 1;
1010  }
1011 
1012  bool LinearConstraintIsFeasible(const ConstraintProto& ct) {
1013  int64_t sum = 0;
1014  const int num_variables = ct.linear().coeffs_size();
1015  for (int i = 0; i < num_variables; ++i) {
1016  sum += Value(ct.linear().vars(i)) * ct.linear().coeffs(i);
1017  }
1018  return DomainInProtoContains(ct.linear(), sum);
1019  }
1020 
1021  bool IntMaxConstraintIsFeasible(const ConstraintProto& ct) {
1022  const int64_t max = Value(ct.int_max().target());
1023  int64_t actual_max = std::numeric_limits<int64_t>::min();
1024  for (int i = 0; i < ct.int_max().vars_size(); ++i) {
1025  actual_max = std::max(actual_max, Value(ct.int_max().vars(i)));
1026  }
1027  return max == actual_max;
1028  }
1029 
1030  int64_t LinearExpressionValue(const LinearExpressionProto& expr) const {
1031  int64_t sum = expr.offset();
1032  const int num_variables = expr.vars_size();
1033  for (int i = 0; i < num_variables; ++i) {
1034  sum += Value(expr.vars(i)) * expr.coeffs(i);
1035  }
1036  return sum;
1037  }
1038 
1039  bool LinMaxConstraintIsFeasible(const ConstraintProto& ct) {
1040  const int64_t max = LinearExpressionValue(ct.lin_max().target());
1041  int64_t actual_max = std::numeric_limits<int64_t>::min();
1042  for (int i = 0; i < ct.lin_max().exprs_size(); ++i) {
1043  const int64_t expr_value = LinearExpressionValue(ct.lin_max().exprs(i));
1044  actual_max = std::max(actual_max, expr_value);
1045  }
1046  return max == actual_max;
1047  }
1048 
1049  bool IntProdConstraintIsFeasible(const ConstraintProto& ct) {
1050  const int64_t prod = Value(ct.int_prod().target());
1051  int64_t actual_prod = 1;
1052  for (int i = 0; i < ct.int_prod().vars_size(); ++i) {
1053  actual_prod *= Value(ct.int_prod().vars(i));
1054  }
1055  return prod == actual_prod;
1056  }
1057 
1058  bool IntDivConstraintIsFeasible(const ConstraintProto& ct) {
1059  return Value(ct.int_div().target()) ==
1060  Value(ct.int_div().vars(0)) / Value(ct.int_div().vars(1));
1061  }
1062 
1063  bool IntModConstraintIsFeasible(const ConstraintProto& ct) {
1064  return Value(ct.int_mod().target()) ==
1065  Value(ct.int_mod().vars(0)) % Value(ct.int_mod().vars(1));
1066  }
1067 
1068  bool IntMinConstraintIsFeasible(const ConstraintProto& ct) {
1069  const int64_t min = Value(ct.int_min().target());
1070  int64_t actual_min = std::numeric_limits<int64_t>::max();
1071  for (int i = 0; i < ct.int_min().vars_size(); ++i) {
1072  actual_min = std::min(actual_min, Value(ct.int_min().vars(i)));
1073  }
1074  return min == actual_min;
1075  }
1076 
1077  bool LinMinConstraintIsFeasible(const ConstraintProto& ct) {
1078  const int64_t min = LinearExpressionValue(ct.lin_min().target());
1079  int64_t actual_min = std::numeric_limits<int64_t>::max();
1080  for (int i = 0; i < ct.lin_min().exprs_size(); ++i) {
1081  const int64_t expr_value = LinearExpressionValue(ct.lin_min().exprs(i));
1082  actual_min = std::min(actual_min, expr_value);
1083  }
1084  return min == actual_min;
1085  }
1086 
1087  bool AllDiffConstraintIsFeasible(const ConstraintProto& ct) {
1088  absl::flat_hash_set<int64_t> values;
1089  for (const int v : ct.all_diff().vars()) {
1090  if (gtl::ContainsKey(values, Value(v))) return false;
1091  values.insert(Value(v));
1092  }
1093  return true;
1094  }
1095 
1096  int64_t IntervalStart(const IntervalConstraintProto& interval) const {
1097  return interval.has_start_view()
1098  ? LinearExpressionValue(interval.start_view())
1099  : Value(interval.start());
1100  }
1101 
1102  int64_t IntervalSize(const IntervalConstraintProto& interval) const {
1103  return interval.has_size_view()
1104  ? LinearExpressionValue(interval.size_view())
1105  : Value(interval.size());
1106  }
1107 
1108  int64_t IntervalEnd(const IntervalConstraintProto& interval) const {
1109  return interval.has_end_view() ? LinearExpressionValue(interval.end_view())
1110  : Value(interval.end());
1111  }
1112 
1113  bool IntervalConstraintIsFeasible(const ConstraintProto& ct) {
1114  const int64_t size = IntervalSize(ct.interval());
1115  if (size < 0) return false;
1116  return IntervalStart(ct.interval()) + size == IntervalEnd(ct.interval());
1117  }
1118 
1119  bool NoOverlapConstraintIsFeasible(const CpModelProto& model,
1120  const ConstraintProto& ct) {
1121  std::vector<std::pair<int64_t, int64_t>> start_durations_pairs;
1122  for (const int i : ct.no_overlap().intervals()) {
1123  const ConstraintProto& interval_constraint = model.constraints(i);
1124  if (ConstraintIsEnforced(interval_constraint)) {
1125  const IntervalConstraintProto& interval =
1126  interval_constraint.interval();
1127  start_durations_pairs.push_back(
1128  {IntervalStart(interval), IntervalSize(interval)});
1129  }
1130  }
1131  std::sort(start_durations_pairs.begin(), start_durations_pairs.end());
1132  int64_t previous_end = std::numeric_limits<int64_t>::min();
1133  for (const auto pair : start_durations_pairs) {
1134  if (pair.first < previous_end) return false;
1135  previous_end = pair.first + pair.second;
1136  }
1137  return true;
1138  }
1139 
1140  bool IntervalsAreDisjoint(const IntervalConstraintProto& interval1,
1141  const IntervalConstraintProto& interval2) {
1142  return IntervalEnd(interval1) <= IntervalStart(interval2) ||
1143  IntervalEnd(interval2) <= IntervalStart(interval1);
1144  }
1145 
1146  bool IntervalIsEmpty(const IntervalConstraintProto& interval) {
1147  return IntervalStart(interval) == IntervalEnd(interval);
1148  }
1149 
1150  bool NoOverlap2DConstraintIsFeasible(const CpModelProto& model,
1151  const ConstraintProto& ct) {
1152  const auto& arg = ct.no_overlap_2d();
1153  // Those intervals from arg.x_intervals and arg.y_intervals where both
1154  // the x and y intervals are enforced.
1155  std::vector<std::pair<const IntervalConstraintProto* const,
1156  const IntervalConstraintProto* const>>
1157  enforced_intervals_xy;
1158  {
1159  const int num_intervals = arg.x_intervals_size();
1160  CHECK_EQ(arg.y_intervals_size(), num_intervals);
1161  for (int i = 0; i < num_intervals; ++i) {
1162  const ConstraintProto& x = model.constraints(arg.x_intervals(i));
1163  const ConstraintProto& y = model.constraints(arg.y_intervals(i));
1164  if (ConstraintIsEnforced(x) && ConstraintIsEnforced(y) &&
1165  (!arg.boxes_with_null_area_can_overlap() ||
1166  (!IntervalIsEmpty(x.interval()) &&
1167  !IntervalIsEmpty(y.interval())))) {
1168  enforced_intervals_xy.push_back({&x.interval(), &y.interval()});
1169  }
1170  }
1171  }
1172  const int num_enforced_intervals = enforced_intervals_xy.size();
1173  for (int i = 0; i < num_enforced_intervals; ++i) {
1174  for (int j = i + 1; j < num_enforced_intervals; ++j) {
1175  const auto& xi = *enforced_intervals_xy[i].first;
1176  const auto& yi = *enforced_intervals_xy[i].second;
1177  const auto& xj = *enforced_intervals_xy[j].first;
1178  const auto& yj = *enforced_intervals_xy[j].second;
1179  if (!IntervalsAreDisjoint(xi, xj) && !IntervalsAreDisjoint(yi, yj) &&
1180  !IntervalIsEmpty(xi) && !IntervalIsEmpty(xj) &&
1181  !IntervalIsEmpty(yi) && !IntervalIsEmpty(yj)) {
1182  VLOG(1) << "Interval " << i << "(x=[" << IntervalStart(xi) << ", "
1183  << IntervalEnd(xi) << "], y=[" << IntervalStart(yi) << ", "
1184  << IntervalEnd(yi) << "]) and " << j << "(x=["
1185  << IntervalStart(xj) << ", " << IntervalEnd(xj) << "], y=["
1186  << IntervalStart(yj) << ", " << IntervalEnd(yj)
1187  << "]) are not disjoint.";
1188  return false;
1189  }
1190  }
1191  }
1192  return true;
1193  }
1194 
1195  bool CumulativeConstraintIsFeasible(const CpModelProto& model,
1196  const ConstraintProto& ct) {
1197  // TODO(user): Improve complexity for large durations.
1198  const int64_t capacity = Value(ct.cumulative().capacity());
1199  const int num_intervals = ct.cumulative().intervals_size();
1200  absl::flat_hash_map<int64_t, int64_t> usage;
1201  for (int i = 0; i < num_intervals; ++i) {
1202  const ConstraintProto& interval_constraint =
1203  model.constraints(ct.cumulative().intervals(i));
1204  if (ConstraintIsEnforced(interval_constraint)) {
1205  const IntervalConstraintProto& interval =
1206  interval_constraint.interval();
1207  const int64_t start = IntervalStart(interval);
1208  const int64_t duration = IntervalSize(interval);
1209  const int64_t demand = Value(ct.cumulative().demands(i));
1210  for (int64_t t = start; t < start + duration; ++t) {
1211  usage[t] += demand;
1212  if (usage[t] > capacity) return false;
1213  }
1214  if (!ct.cumulative().energies().empty()) {
1215  const LinearExpressionProto& energy_expr =
1216  ct.cumulative().energies(i);
1217  int64_t energy = energy_expr.offset();
1218  for (int j = 0; j < energy_expr.vars_size(); ++j) {
1219  energy += Value(energy_expr.vars(j)) * energy_expr.coeffs(j);
1220  }
1221  if (duration * demand != energy) {
1222  return false;
1223  }
1224  }
1225  }
1226  }
1227  return true;
1228  }
1229 
1230  bool ElementConstraintIsFeasible(const ConstraintProto& ct) {
1231  if (ct.element().vars().empty()) return false;
1232  const int index = Value(ct.element().index());
1233  if (index < 0 || index >= ct.element().vars_size()) return false;
1234  return Value(ct.element().vars(index)) == Value(ct.element().target());
1235  }
1236 
1237  bool TableConstraintIsFeasible(const ConstraintProto& ct) {
1238  const int size = ct.table().vars_size();
1239  if (size == 0) return true;
1240  for (int row_start = 0; row_start < ct.table().values_size();
1241  row_start += size) {
1242  int i = 0;
1243  while (Value(ct.table().vars(i)) == ct.table().values(row_start + i)) {
1244  ++i;
1245  if (i == size) return !ct.table().negated();
1246  }
1247  }
1248  return ct.table().negated();
1249  }
1250 
1251  bool AutomatonConstraintIsFeasible(const ConstraintProto& ct) {
1252  // Build the transition table {tail, label} -> head.
1253  absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> transition_map;
1254  const int num_transitions = ct.automaton().transition_tail().size();
1255  for (int i = 0; i < num_transitions; ++i) {
1256  transition_map[{ct.automaton().transition_tail(i),
1257  ct.automaton().transition_label(i)}] =
1258  ct.automaton().transition_head(i);
1259  }
1260 
1261  // Walk the automaton.
1262  int64_t current_state = ct.automaton().starting_state();
1263  const int num_steps = ct.automaton().vars_size();
1264  for (int i = 0; i < num_steps; ++i) {
1265  const std::pair<int64_t, int64_t> key = {current_state,
1266  Value(ct.automaton().vars(i))};
1267  if (!gtl::ContainsKey(transition_map, key)) {
1268  return false;
1269  }
1270  current_state = transition_map[key];
1271  }
1272 
1273  // Check we are now in a final state.
1274  for (const int64_t final : ct.automaton().final_states()) {
1275  if (current_state == final) return true;
1276  }
1277  return false;
1278  }
1279 
1280  bool CircuitConstraintIsFeasible(const ConstraintProto& ct) {
1281  // Compute the set of relevant nodes for the constraint and set the next of
1282  // each of them. This also detects duplicate nexts.
1283  const int num_arcs = ct.circuit().tails_size();
1284  absl::flat_hash_set<int> nodes;
1285  absl::flat_hash_map<int, int> nexts;
1286  for (int i = 0; i < num_arcs; ++i) {
1287  const int tail = ct.circuit().tails(i);
1288  const int head = ct.circuit().heads(i);
1289  nodes.insert(tail);
1290  nodes.insert(head);
1291  if (LiteralIsFalse(ct.circuit().literals(i))) continue;
1292  if (nexts.contains(tail)) {
1293  VLOG(1) << "Node with two outgoing arcs";
1294  return false; // Duplicate.
1295  }
1296  nexts[tail] = head;
1297  }
1298 
1299  // All node must have a next.
1300  int in_cycle;
1301  int cycle_size = 0;
1302  for (const int node : nodes) {
1303  if (!nexts.contains(node)) {
1304  VLOG(1) << "Node with no next: " << node;
1305  return false; // No next.
1306  }
1307  if (nexts[node] == node) continue; // skip self-loop.
1308  in_cycle = node;
1309  ++cycle_size;
1310  }
1311  if (cycle_size == 0) return true;
1312 
1313  // Check that we have only one cycle. visited is used to not loop forever if
1314  // we have a "rho" shape instead of a cycle.
1315  absl::flat_hash_set<int> visited;
1316  int current = in_cycle;
1317  int num_visited = 0;
1318  while (!visited.contains(current)) {
1319  ++num_visited;
1320  visited.insert(current);
1321  current = nexts[current];
1322  }
1323  if (current != in_cycle) {
1324  VLOG(1) << "Rho shape";
1325  return false; // Rho shape.
1326  }
1327  if (num_visited != cycle_size) {
1328  VLOG(1) << "More than one cycle";
1329  }
1330  return num_visited == cycle_size; // Another cycle somewhere if false.
1331  }
1332 
1333  bool RoutesConstraintIsFeasible(const ConstraintProto& ct) {
1334  const int num_arcs = ct.routes().tails_size();
1335  int num_used_arcs = 0;
1336  int num_self_arcs = 0;
1337  int num_nodes = 0;
1338  std::vector<int> tail_to_head;
1339  std::vector<int> depot_nexts;
1340  for (int i = 0; i < num_arcs; ++i) {
1341  const int tail = ct.routes().tails(i);
1342  const int head = ct.routes().heads(i);
1343  num_nodes = std::max(num_nodes, 1 + tail);
1344  num_nodes = std::max(num_nodes, 1 + head);
1345  tail_to_head.resize(num_nodes, -1);
1346  if (LiteralIsTrue(ct.routes().literals(i))) {
1347  if (tail == head) {
1348  if (tail == 0) return false;
1349  ++num_self_arcs;
1350  continue;
1351  }
1352  ++num_used_arcs;
1353  if (tail == 0) {
1354  depot_nexts.push_back(head);
1355  } else {
1356  if (tail_to_head[tail] != -1) return false;
1357  tail_to_head[tail] = head;
1358  }
1359  }
1360  }
1361 
1362  // An empty constraint with no node to visit should be feasible.
1363  if (num_nodes == 0) return true;
1364 
1365  // Make sure each routes from the depot go back to it, and count such arcs.
1366  int count = 0;
1367  for (int start : depot_nexts) {
1368  ++count;
1369  while (start != 0) {
1370  if (tail_to_head[start] == -1) return false;
1371  start = tail_to_head[start];
1372  ++count;
1373  }
1374  }
1375 
1376  if (count != num_used_arcs) {
1377  VLOG(1) << "count: " << count << " != num_used_arcs:" << num_used_arcs;
1378  return false;
1379  }
1380 
1381  // Each routes cover as many node as there is arcs, but this way we count
1382  // multiple times the depot. So the number of nodes covered are:
1383  // count - depot_nexts.size() + 1.
1384  // And this number + the self arcs should be num_nodes.
1385  if (count - depot_nexts.size() + 1 + num_self_arcs != num_nodes) {
1386  VLOG(1) << "Not all nodes are covered!";
1387  return false;
1388  }
1389 
1390  return true;
1391  }
1392 
1393  bool InverseConstraintIsFeasible(const ConstraintProto& ct) {
1394  const int num_variables = ct.inverse().f_direct_size();
1395  if (num_variables != ct.inverse().f_inverse_size()) return false;
1396  // Check that f_inverse(f_direct(i)) == i; this is sufficient.
1397  for (int i = 0; i < num_variables; i++) {
1398  const int fi = Value(ct.inverse().f_direct(i));
1399  if (fi < 0 || num_variables <= fi) return false;
1400  if (i != Value(ct.inverse().f_inverse(fi))) return false;
1401  }
1402  return true;
1403  }
1404 
1405  bool ReservoirConstraintIsFeasible(const ConstraintProto& ct) {
1406  const int num_variables = ct.reservoir().times_size();
1407  const int64_t min_level = ct.reservoir().min_level();
1408  const int64_t max_level = ct.reservoir().max_level();
1409  std::map<int64_t, int64_t> deltas;
1410  const bool has_active_variables = ct.reservoir().actives_size() > 0;
1411  for (int i = 0; i < num_variables; i++) {
1412  const int64_t time = Value(ct.reservoir().times(i));
1413  if (!has_active_variables || Value(ct.reservoir().actives(i)) == 1) {
1414  deltas[time] += ct.reservoir().demands(i);
1415  }
1416  }
1417  int64_t current_level = 0;
1418  for (const auto& delta : deltas) {
1419  current_level += delta.second;
1420  if (current_level < min_level || current_level > max_level) {
1421  VLOG(1) << "Reservoir level " << current_level
1422  << " is out of bounds at time" << delta.first;
1423  return false;
1424  }
1425  }
1426  return true;
1427  }
1428 
1429  private:
1430  std::vector<int64_t> variable_values_;
1431 };
1432 
1433 } // namespace
1434 
1436  const std::vector<int64_t>& variable_values,
1437  const CpModelProto* mapping_proto,
1438  const std::vector<int>* postsolve_mapping) {
1439  if (variable_values.size() != model.variables_size()) {
1440  VLOG(1) << "Wrong number of variables in the solution vector";
1441  return false;
1442  }
1443 
1444  // Check that all values fall in the variable domains.
1445  for (int i = 0; i < model.variables_size(); ++i) {
1446  if (!DomainInProtoContains(model.variables(i), variable_values[i])) {
1447  VLOG(1) << "Variable #" << i << " has value " << variable_values[i]
1448  << " which do not fall in its domain: "
1449  << ProtobufShortDebugString(model.variables(i));
1450  return false;
1451  }
1452  }
1453 
1454  CHECK_EQ(variable_values.size(), model.variables_size());
1455  ConstraintChecker checker(variable_values);
1456 
1457  for (int c = 0; c < model.constraints_size(); ++c) {
1458  const ConstraintProto& ct = model.constraints(c);
1459 
1460  if (!checker.ConstraintIsEnforced(ct)) continue;
1461 
1462  bool is_feasible = true;
1463  const ConstraintProto::ConstraintCase type = ct.constraint_case();
1464  switch (type) {
1465  case ConstraintProto::ConstraintCase::kBoolOr:
1466  is_feasible = checker.BoolOrConstraintIsFeasible(ct);
1467  break;
1468  case ConstraintProto::ConstraintCase::kBoolAnd:
1469  is_feasible = checker.BoolAndConstraintIsFeasible(ct);
1470  break;
1471  case ConstraintProto::ConstraintCase::kAtMostOne:
1472  is_feasible = checker.AtMostOneConstraintIsFeasible(ct);
1473  break;
1474  case ConstraintProto::ConstraintCase::kExactlyOne:
1475  is_feasible = checker.ExactlyOneConstraintIsFeasible(ct);
1476  break;
1477  case ConstraintProto::ConstraintCase::kBoolXor:
1478  is_feasible = checker.BoolXorConstraintIsFeasible(ct);
1479  break;
1480  case ConstraintProto::ConstraintCase::kLinear:
1481  is_feasible = checker.LinearConstraintIsFeasible(ct);
1482  break;
1483  case ConstraintProto::ConstraintCase::kIntProd:
1484  is_feasible = checker.IntProdConstraintIsFeasible(ct);
1485  break;
1486  case ConstraintProto::ConstraintCase::kIntDiv:
1487  is_feasible = checker.IntDivConstraintIsFeasible(ct);
1488  break;
1489  case ConstraintProto::ConstraintCase::kIntMod:
1490  is_feasible = checker.IntModConstraintIsFeasible(ct);
1491  break;
1492  case ConstraintProto::ConstraintCase::kIntMin:
1493  is_feasible = checker.IntMinConstraintIsFeasible(ct);
1494  break;
1495  case ConstraintProto::ConstraintCase::kLinMin:
1496  is_feasible = checker.LinMinConstraintIsFeasible(ct);
1497  break;
1498  case ConstraintProto::ConstraintCase::kIntMax:
1499  is_feasible = checker.IntMaxConstraintIsFeasible(ct);
1500  break;
1501  case ConstraintProto::ConstraintCase::kLinMax:
1502  is_feasible = checker.LinMaxConstraintIsFeasible(ct);
1503  break;
1504  case ConstraintProto::ConstraintCase::kAllDiff:
1505  is_feasible = checker.AllDiffConstraintIsFeasible(ct);
1506  break;
1507  case ConstraintProto::ConstraintCase::kInterval:
1508  if (!checker.IntervalConstraintIsFeasible(ct)) {
1509  if (ct.interval().has_start_view()) {
1510  // Tricky: For simplified presolve, we require that a separate
1511  // constraint is added to the model to enforce the "interval".
1512  // This indicates that such a constraint was not added to the model.
1513  // It should probably be a validation error, but it is hard to
1514  // detect beforehand.
1515  LOG(ERROR) << "Warning, an interval constraint was likely used "
1516  "without a corresponding linear constraint linking "
1517  "its start, size and end.";
1518  } else {
1519  is_feasible = false;
1520  }
1521  }
1522  break;
1523  case ConstraintProto::ConstraintCase::kNoOverlap:
1524  is_feasible = checker.NoOverlapConstraintIsFeasible(model, ct);
1525  break;
1526  case ConstraintProto::ConstraintCase::kNoOverlap2D:
1527  is_feasible = checker.NoOverlap2DConstraintIsFeasible(model, ct);
1528  break;
1529  case ConstraintProto::ConstraintCase::kCumulative:
1530  is_feasible = checker.CumulativeConstraintIsFeasible(model, ct);
1531  break;
1532  case ConstraintProto::ConstraintCase::kElement:
1533  is_feasible = checker.ElementConstraintIsFeasible(ct);
1534  break;
1535  case ConstraintProto::ConstraintCase::kTable:
1536  is_feasible = checker.TableConstraintIsFeasible(ct);
1537  break;
1538  case ConstraintProto::ConstraintCase::kAutomaton:
1539  is_feasible = checker.AutomatonConstraintIsFeasible(ct);
1540  break;
1541  case ConstraintProto::ConstraintCase::kCircuit:
1542  is_feasible = checker.CircuitConstraintIsFeasible(ct);
1543  break;
1544  case ConstraintProto::ConstraintCase::kRoutes:
1545  is_feasible = checker.RoutesConstraintIsFeasible(ct);
1546  break;
1547  case ConstraintProto::ConstraintCase::kInverse:
1548  is_feasible = checker.InverseConstraintIsFeasible(ct);
1549  break;
1550  case ConstraintProto::ConstraintCase::kReservoir:
1551  is_feasible = checker.ReservoirConstraintIsFeasible(ct);
1552  break;
1553  case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1554  // Empty constraint is always feasible.
1555  break;
1556  default:
1557  LOG(FATAL) << "Unuspported constraint: " << ConstraintCaseName(type);
1558  }
1559 
1560  // Display a message to help debugging.
1561  if (!is_feasible) {
1562  VLOG(1) << "Failing constraint #" << c << " : "
1563  << ProtobufShortDebugString(model.constraints(c));
1564  if (mapping_proto != nullptr && postsolve_mapping != nullptr) {
1565  std::vector<int> reverse_map(mapping_proto->variables().size(), -1);
1566  for (int var = 0; var < postsolve_mapping->size(); ++var) {
1567  reverse_map[(*postsolve_mapping)[var]] = var;
1568  }
1569  for (const int var : UsedVariables(model.constraints(c))) {
1570  VLOG(1) << "var: " << var << " mapped_to: " << reverse_map[var]
1571  << " value: " << variable_values[var] << " initial_domain: "
1572  << ReadDomainFromProto(model.variables(var))
1573  << " postsolved_domain: "
1574  << ReadDomainFromProto(mapping_proto->variables(var));
1575  }
1576  } else {
1577  for (const int var : UsedVariables(model.constraints(c))) {
1578  VLOG(1) << "var: " << var << " value: " << variable_values[var];
1579  }
1580  }
1581  return false;
1582  }
1583  }
1584 
1585  // Check that the objective is within its domain.
1586  //
1587  // TODO(user): This is not really a "feasibility" question, but we should
1588  // probably check that the response objective matches with the one we can
1589  // compute here. This might better be done in another function though.
1590  if (model.has_objective()) {
1591  int64_t inner_objective = 0;
1592  const int num_variables = model.objective().coeffs_size();
1593  for (int i = 0; i < num_variables; ++i) {
1594  inner_objective += checker.Value(model.objective().vars(i)) *
1595  model.objective().coeffs(i);
1596  }
1597  if (!model.objective().domain().empty()) {
1598  if (!DomainInProtoContains(model.objective(), inner_objective)) {
1599  VLOG(1) << "Objective value not in domain!";
1600  return false;
1601  }
1602  }
1603  double factor = model.objective().scaling_factor();
1604  if (factor == 0.0) factor = 1.0;
1605  const double scaled_objective =
1606  factor *
1607  (static_cast<double>(inner_objective) + model.objective().offset());
1608  VLOG(2) << "Checker inner objective = " << inner_objective;
1609  VLOG(2) << "Checker scaled objective = " << scaled_objective;
1610  }
1611 
1612  return true;
1613 }
1614 
1615 } // namespace sat
1616 } // namespace operations_research
int64_t head
std::vector< int > UsedVariables(const ConstraintProto &ct)
static constexpr DomainReductionStrategy SELECT_MIN_VALUE
Definition: cp_model.pb.h:5158
int64_t min
Definition: alldiff_cst.cc:139
const int FATAL
Definition: log_severity.h:32
static constexpr DomainReductionStrategy SELECT_MEDIAN_VALUE
Definition: cp_model.pb.h:5166
static constexpr VariableSelectionStrategy CHOOSE_LOWEST_MIN
Definition: cp_model.pb.h:5124
#define VLOG(verboselevel)
Definition: base/logging.h:979
const int ERROR
Definition: log_severity.h:32
#define LOG(severity)
Definition: base/logging.h:416
GRBmodel * model
int64_t CapProd(int64_t x, int64_t y)
static constexpr VariableSelectionStrategy CHOOSE_HIGHEST_MAX
Definition: cp_model.pb.h:5126
int64_t tail
static constexpr VariableSelectionStrategy CHOOSE_FIRST
Definition: cp_model.pb.h:5122
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition: integer.h:1544
std::string ProtobufDebugString(const P &message)
std::string ValidateCpModel(const CpModelProto &model)
int64_t max
Definition: alldiff_cst.cc:140
static constexpr VariableSelectionStrategy CHOOSE_MAX_DOMAIN_SIZE
Definition: cp_model.pb.h:5130
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
CpModelProto proto
int64_t CapAdd(int64_t x, int64_t y)
int64_t demand
Definition: resource.cc:125
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
int64_t energy
Definition: resource.cc:354
static int input(yyscan_t yyscanner)
int64_t capacity
int index
Definition: pack.cc:509
int64_t Size() const
Returns the number of elements in the domain.
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
static constexpr DomainReductionStrategy SELECT_MAX_VALUE
Definition: cp_model.pb.h:5160
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
std::vector< int > UsedIntervals(const ConstraintProto &ct)
int64_t delta
Definition: resource.cc:1692
static constexpr DomainReductionStrategy SELECT_UPPER_HALF
Definition: cp_model.pb.h:5164
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
We call domain any subset of Int64 = [kint64min, kint64max].
static constexpr VariableSelectionStrategy CHOOSE_MIN_DOMAIN_SIZE
Definition: cp_model.pb.h:5128
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
std::string ProtobufShortDebugString(const P &message)
#define RETURN_IF_NOT_EMPTY(statement)
Collection of objects used to extend the Constraint Solver library.
int64_t time
Definition: resource.cc:1691
bool RefIsPositive(int ref)
IntVar * var
Definition: expr_array.cc:1874
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
Returns true iff we have:
static constexpr DomainReductionStrategy SELECT_LOWER_HALF
Definition: cp_model.pb.h:5162
int nodes
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
IntervalVar * interval
Definition: resource.cc:100
const Constraint * ct
bool SolutionIsFeasible(const CpModelProto &model, const std::vector< int64_t > &variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)