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