OR-Tools  9.6
cp_model_checker.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdint>
19 #include <cstdlib>
20 #include <limits>
21 #include <string>
22 #include <utility>
23 #include <vector>
24 
25 #include "absl/container/btree_map.h"
26 #include "absl/container/flat_hash_map.h"
27 #include "absl/container/flat_hash_set.h"
28 #include "absl/meta/type_traits.h"
29 #include "absl/strings/str_cat.h"
30 #include "ortools/base/logging.h"
32 #include "ortools/sat/cp_model.pb.h"
34 #include "ortools/sat/sat_parameters.pb.h"
37 
38 namespace operations_research {
39 namespace sat {
40 namespace {
41 
42 // =============================================================================
43 // CpModelProto validation.
44 // =============================================================================
45 
46 // If the string returned by "statement" is not empty, returns it.
47 #define RETURN_IF_NOT_EMPTY(statement) \
48  do { \
49  const std::string error_message = statement; \
50  if (!error_message.empty()) return error_message; \
51  } while (false)
52 
53 template <typename ProtoWithDomain>
54 bool DomainInProtoIsValid(const ProtoWithDomain& proto) {
55  if (proto.domain().size() % 2) return false;
56  std::vector<ClosedInterval> domain;
57  for (int i = 0; i < proto.domain_size(); i += 2) {
58  if (proto.domain(i) > proto.domain(i + 1)) return false;
59  domain.push_back({proto.domain(i), proto.domain(i + 1)});
60  }
61  return IntervalsAreSortedAndNonAdjacent(domain);
62 }
63 
64 bool VariableReferenceIsValid(const CpModelProto& model, int reference) {
65  // We do it this way to avoid overflow if reference is kint64min for instance.
66  if (reference >= model.variables_size()) return false;
67  return reference >= -static_cast<int>(model.variables_size());
68 }
69 
70 // Note(user): Historically we always accepted positive or negative variable
71 // reference everywhere, but now that we can always substitute affine relation,
72 // we starts to transition to positive reference only, which are clearer. Note
73 // that this doesn't concern literal reference though.
74 bool VariableIndexIsValid(const CpModelProto& model, int var) {
75  return var >= 0 && var < model.variables_size();
76 }
77 
78 bool LiteralReferenceIsValid(const CpModelProto& model, int reference) {
79  if (!VariableReferenceIsValid(model, reference)) return false;
80  const auto& var_proto = model.variables(PositiveRef(reference));
81  const int64_t min_domain = var_proto.domain(0);
82  const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
83  return min_domain >= 0 && max_domain <= 1;
84 }
85 
86 std::string ValidateIntegerVariable(const CpModelProto& model, int v) {
87  const IntegerVariableProto& proto = model.variables(v);
88  if (proto.domain_size() == 0) {
89  return absl::StrCat("var #", v,
90  " has no domain(): ", ProtobufShortDebugString(proto));
91  }
92  if (proto.domain_size() % 2 != 0) {
93  return absl::StrCat("var #", v, " has an odd domain() size: ",
95  }
96  if (!DomainInProtoIsValid(proto)) {
97  return absl::StrCat("var #", v, " has and invalid domain() format: ",
99  }
100 
101  // Internally, we often take the negation of a domain, and we also want to
102  // have sentinel values greater than the min/max of a variable domain, so
103  // the domain must fall in [kint64min + 2, kint64max - 1].
104  const int64_t lb = proto.domain(0);
105  const int64_t ub = proto.domain(proto.domain_size() - 1);
106  if (lb < std::numeric_limits<int64_t>::min() + 2 ||
108  return absl::StrCat(
109  "var #", v, " domain do not fall in [kint64min + 2, kint64max - 1]. ",
111  }
112 
113  // We do compute ub - lb in some place in the code and do not want to deal
114  // with overflow everywhere. This seems like a reasonable precondition anyway.
115  if (lb < 0 && lb + std::numeric_limits<int64_t>::max() < ub) {
116  return absl::StrCat(
117  "var #", v,
118  " has a domain that is too large, i.e. |UB - LB| overflow an int64_t: ",
120  }
121 
122  return "";
123 }
124 
125 std::string ValidateVariablesUsedInConstraint(const CpModelProto& model,
126  int c) {
127  const ConstraintProto& ct = model.constraints(c);
128  IndexReferences references = GetReferencesUsedByConstraint(ct);
129  for (const int v : references.variables) {
130  if (!VariableReferenceIsValid(model, v)) {
131  return absl::StrCat("Out of bound integer variable ", v,
132  " in constraint #", c, " : ",
134  }
135  }
136  for (const int lit : ct.enforcement_literal()) {
137  if (!LiteralReferenceIsValid(model, lit)) {
138  return absl::StrCat("Invalid enforcement literal ", lit,
139  " in constraint #", c, " : ",
141  }
142  }
143  for (const int lit : references.literals) {
144  if (!LiteralReferenceIsValid(model, lit)) {
145  return absl::StrCat("Invalid literal ", lit, " in constraint #", c, " : ",
147  }
148  }
149  return "";
150 }
151 
152 std::string ValidateIntervalsUsedInConstraint(bool after_presolve,
153  const CpModelProto& model,
154  int c) {
155  const ConstraintProto& ct = model.constraints(c);
156  for (const int i : UsedIntervals(ct)) {
157  if (i < 0 || i >= model.constraints_size()) {
158  return absl::StrCat("Out of bound interval ", i, " in constraint #", c,
159  " : ", ProtobufShortDebugString(ct));
160  }
161  if (after_presolve && i >= c) {
162  return absl::StrCat("Interval ", i, " in constraint #", c,
163  " must appear before in the list of constraints :",
165  }
166  if (model.constraints(i).constraint_case() !=
167  ConstraintProto::ConstraintCase::kInterval) {
168  return absl::StrCat(
169  "Interval ", i,
170  " does not refer to an interval constraint. Problematic constraint #",
171  c, " : ", ProtobufShortDebugString(ct));
172  }
173  }
174  return "";
175 }
176 
177 int64_t MinOfRef(const CpModelProto& model, int ref) {
178  const IntegerVariableProto& var_proto = model.variables(PositiveRef(ref));
179  if (RefIsPositive(ref)) {
180  return var_proto.domain(0);
181  } else {
182  return -var_proto.domain(var_proto.domain_size() - 1);
183  }
184 }
185 
186 int64_t MaxOfRef(const CpModelProto& model, int ref) {
187  const IntegerVariableProto& var_proto = model.variables(PositiveRef(ref));
188  if (RefIsPositive(ref)) {
189  return var_proto.domain(var_proto.domain_size() - 1);
190  } else {
191  return -var_proto.domain(0);
192  }
193 }
194 
195 template <class LinearExpressionProto>
196 int64_t MinOfExpression(const CpModelProto& model,
197  const LinearExpressionProto& proto) {
198  int64_t sum_min = proto.offset();
199  for (int i = 0; i < proto.vars_size(); ++i) {
200  const int ref = proto.vars(i);
201  const int64_t coeff = proto.coeffs(i);
202  sum_min =
203  CapAdd(sum_min, coeff >= 0 ? CapProd(MinOfRef(model, ref), coeff)
204  : CapProd(MaxOfRef(model, ref), coeff));
205  }
206 
207  return sum_min;
208 }
209 
210 template <class LinearExpressionProto>
211 int64_t MaxOfExpression(const CpModelProto& model,
212  const LinearExpressionProto& proto) {
213  int64_t sum_max = proto.offset();
214  for (int i = 0; i < proto.vars_size(); ++i) {
215  const int ref = proto.vars(i);
216  const int64_t coeff = proto.coeffs(i);
217  sum_max =
218  CapAdd(sum_max, coeff >= 0 ? CapProd(MaxOfRef(model, ref), coeff)
219  : CapProd(MinOfRef(model, ref), coeff));
220  }
221 
222  return sum_max;
223 }
224 
225 int64_t IntervalSizeMin(const CpModelProto& model, int interval_index) {
226  DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
227  model.constraints(interval_index).constraint_case());
228  const IntervalConstraintProto& proto =
229  model.constraints(interval_index).interval();
230  return MinOfExpression(model, proto.size());
231 }
232 
233 int64_t IntervalSizeMax(const CpModelProto& model, int interval_index) {
234  DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
235  model.constraints(interval_index).constraint_case());
236  const IntervalConstraintProto& proto =
237  model.constraints(interval_index).interval();
238  return MaxOfExpression(model, proto.size());
239 }
240 
241 Domain DomainOfRef(const CpModelProto& model, int ref) {
242  const Domain domain = ReadDomainFromProto(model.variables(PositiveRef(ref)));
243  return RefIsPositive(ref) ? domain : domain.Negation();
244 }
245 
246 std::string ValidateLinearExpression(const CpModelProto& model,
247  const LinearExpressionProto& expr) {
248  if (expr.coeffs_size() != expr.vars_size()) {
249  return absl::StrCat("coeffs_size() != vars_size() in linear expression: ",
251  }
252  if (PossibleIntegerOverflow(model, expr.vars(), expr.coeffs(),
253  expr.offset())) {
254  return absl::StrCat("Possible overflow in linear expression: ",
256  }
257  return "";
258 }
259 
260 std::string ValidateAffineExpression(const CpModelProto& model,
261  const LinearExpressionProto& expr) {
262  if (expr.vars_size() > 1) {
263  return absl::StrCat("expression must be affine: ",
265  }
266  return ValidateLinearExpression(model, expr);
267 }
268 
269 std::string ValidateConstantAffineExpression(
270  const CpModelProto& model, const LinearExpressionProto& expr) {
271  if (!expr.vars().empty()) {
272  return absl::StrCat("expression must be constant: ",
274  }
275  return ValidateLinearExpression(model, expr);
276 }
277 
278 std::string ValidateLinearConstraint(const CpModelProto& model,
279  const ConstraintProto& ct) {
280  if (!DomainInProtoIsValid(ct.linear())) {
281  return absl::StrCat("Invalid domain in constraint : ",
283  }
284  if (ct.linear().coeffs_size() != ct.linear().vars_size()) {
285  return absl::StrCat("coeffs_size() != vars_size() in constraint: ",
287  }
288  const LinearConstraintProto& arg = ct.linear();
289  if (PossibleIntegerOverflow(model, arg.vars(), arg.coeffs())) {
290  return "Possible integer overflow in constraint: " +
292  }
293  return "";
294 }
295 
296 std::string ValidateIntModConstraint(const CpModelProto& model,
297  const ConstraintProto& ct) {
298  if (ct.int_mod().exprs().size() != 2) {
299  return absl::StrCat("An int_mod constraint should have exactly 2 terms: ",
301  }
302  if (!ct.int_mod().has_target()) {
303  return absl::StrCat("An int_mod constraint should have a target: ",
305  }
306 
307  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_mod().exprs(0)));
308  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_mod().exprs(1)));
309  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_mod().target()));
310 
311  const LinearExpressionProto mod_expr = ct.int_mod().exprs(1);
312  if (MinOfExpression(model, mod_expr) <= 0) {
313  return absl::StrCat(
314  "An int_mod must have a strictly positive modulo argument: ",
316  }
317 
318  return "";
319 }
320 
321 std::string ValidateIntProdConstraint(const CpModelProto& model,
322  const ConstraintProto& ct) {
323  if (ct.int_prod().exprs().size() != 2) {
324  return absl::StrCat("An int_prod constraint should have exactly 2 terms: ",
326  }
327  if (!ct.int_prod().has_target()) {
328  return absl::StrCat("An int_prod constraint should have a target: ",
330  }
331 
332  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_prod().exprs(0)));
333  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_prod().exprs(1)));
334  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_prod().target()));
335 
336  // Detect potential overflow if some of the variables span across 0.
337  const LinearExpressionProto& expr0 = ct.int_prod().exprs(0);
338  const LinearExpressionProto& expr1 = ct.int_prod().exprs(1);
339  const Domain product_domain =
340  Domain({MinOfExpression(model, expr0), MaxOfExpression(model, expr0)})
341  .ContinuousMultiplicationBy(Domain(
342  {MinOfExpression(model, expr1), MaxOfExpression(model, expr1)}));
343  if ((product_domain.Max() == std::numeric_limits<int64_t>::max() &&
344  product_domain.Min() < 0) ||
345  (product_domain.Min() == std::numeric_limits<int64_t>::min() &&
346  product_domain.Max() > 0)) {
347  return absl::StrCat("Potential integer overflow in constraint: ",
349  }
350  return "";
351 }
352 
353 std::string ValidateIntDivConstraint(const CpModelProto& model,
354  const ConstraintProto& ct) {
355  if (ct.int_div().exprs().size() != 2) {
356  return absl::StrCat("An int_div constraint should have exactly 2 terms: ",
358  }
359  if (!ct.int_div().has_target()) {
360  return absl::StrCat("An int_div constraint should have a target: ",
362  }
363 
364  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_div().exprs(0)));
365  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_div().exprs(1)));
366  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, ct.int_div().target()));
367 
368  const LinearExpressionProto& divisor_proto = ct.int_div().exprs(1);
369  if (MinOfExpression(model, divisor_proto) <= 0 &&
370  MaxOfExpression(model, divisor_proto) >= 0) {
371  return absl::StrCat("The divisor cannot span across zero in constraint: ",
373  }
374 
375  return "";
376 }
377 
378 std::string ValidateElementConstraint(const CpModelProto& model,
379  const ConstraintProto& ct) {
380  const ElementConstraintProto& element = ct.element();
381 
382  // We need to be able to manipulate expression like "target - var" without
383  // integer overflow.
384  LinearExpressionProto overflow_detection;
385  overflow_detection.add_vars(element.target());
386  overflow_detection.add_coeffs(1);
387  overflow_detection.add_vars(/*dummy*/ 0);
388  overflow_detection.add_coeffs(-1);
389  for (const int ref : element.vars()) {
390  overflow_detection.set_vars(1, ref);
391  if (PossibleIntegerOverflow(model, overflow_detection.vars(),
392  overflow_detection.coeffs())) {
393  return absl::StrCat(
394  "Domain of the variables involved in element constraint may cause "
395  "overflow",
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  if (label <= std::numeric_limits<int64_t>::min() + 1 ||
431  return absl::StrCat("labels in the automaton constraint are too big: ",
432  label);
433  }
434  const auto [it, inserted] =
435  tail_label_to_head.insert({{tail, label}, head});
436  if (!inserted) {
437  if (it->second == head) {
438  return absl::StrCat("automaton: duplicate transition ", tail, " --(",
439  label, ")--> ", head);
440  } else {
441  return absl::StrCat("automaton: incompatible transitions ", tail,
442  " --(", label, ")--> ", head, " and ", tail, " --(",
443  label, ")--> ", it->second);
444  }
445  }
446  }
447  return "";
448 }
449 
450 template <typename GraphProto>
451 std::string ValidateGraphInput(bool is_route, const CpModelProto& model,
452  const GraphProto& graph) {
453  const int size = graph.tails().size();
454  if (graph.heads().size() != size || graph.literals().size() != size) {
455  return absl::StrCat("Wrong field sizes in graph: ",
456  ProtobufShortDebugString(graph));
457  }
458 
459  // We currently disallow multiple self-loop on the same node.
460  absl::flat_hash_set<int> self_loops;
461  for (int i = 0; i < size; ++i) {
462  if (graph.heads(i) != graph.tails(i)) continue;
463  if (!self_loops.insert(graph.heads(i)).second) {
464  return absl::StrCat(
465  "Circuit/Route constraint contains multiple self-loop involving "
466  "node ",
467  graph.heads(i));
468  }
469  if (is_route && graph.tails(i) == 0) {
470  return absl::StrCat(
471  "A route constraint cannot have a self-loop on the depot (node 0)");
472  }
473  }
474 
475  return "";
476 }
477 
478 std::string ValidateRoutesConstraint(const CpModelProto& model,
479  const ConstraintProto& ct) {
480  int max_node = 0;
481  absl::flat_hash_set<int> nodes;
482  for (const int node : ct.routes().tails()) {
483  if (node < 0) {
484  return "All node in a route constraint must be in [0, num_nodes)";
485  }
486  nodes.insert(node);
487  max_node = std::max(max_node, node);
488  }
489  for (const int node : ct.routes().heads()) {
490  if (node < 0) {
491  return "All node in a route constraint must be in [0, num_nodes)";
492  }
493  nodes.insert(node);
494  max_node = std::max(max_node, node);
495  }
496  if (!nodes.empty() && max_node != nodes.size() - 1) {
497  return absl::StrCat(
498  "All nodes in a route constraint must have incident arcs");
499  }
500 
501  return ValidateGraphInput(/*is_route=*/true, model, ct.routes());
502 }
503 
504 std::string ValidateDomainIsPositive(const CpModelProto& model, int ref,
505  const std::string& ref_name) {
506  if (ref < 0) {
507  const IntegerVariableProto& var_proto = model.variables(NegatedRef(ref));
508  if (var_proto.domain(var_proto.domain_size() - 1) > 0) {
509  return absl::StrCat("Negative value in ", ref_name,
510  " domain: negation of ",
511  ProtobufDebugString(var_proto));
512  }
513  } else {
514  const IntegerVariableProto& var_proto = model.variables(ref);
515  if (var_proto.domain(0) < 0) {
516  return absl::StrCat("Negative value in ", ref_name,
517  " domain: ", ProtobufDebugString(var_proto));
518  }
519  }
520  return "";
521 }
522 
523 void AppendToOverflowValidator(const LinearExpressionProto& input,
524  LinearExpressionProto* output) {
525  output->mutable_vars()->Add(input.vars().begin(), input.vars().end());
526  output->mutable_coeffs()->Add(input.coeffs().begin(), input.coeffs().end());
527 
528  // We add the absolute value to be sure that future computation will not
529  // overflow depending on the order they are performed in.
530  output->set_offset(
531  CapAdd(std::abs(output->offset()), std::abs(input.offset())));
532 }
533 
534 std::string ValidateIntervalConstraint(const CpModelProto& model,
535  const ConstraintProto& ct) {
536  if (ct.enforcement_literal().size() > 1) {
537  return absl::StrCat(
538  "Interval with more than one enforcement literals are currently not "
539  "supported: ",
541  }
542  const IntervalConstraintProto& arg = ct.interval();
543 
544  if (!arg.has_start()) {
545  return absl::StrCat("Interval must have a start expression: ",
547  }
548  if (!arg.has_size()) {
549  return absl::StrCat("Interval must have a size expression: ",
551  }
552  if (!arg.has_end()) {
553  return absl::StrCat("Interval must have a end expression: ",
555  }
556 
557  LinearExpressionProto for_overflow_validation;
558  if (arg.start().vars_size() > 1) {
559  return "Interval with a start expression containing more than one "
560  "variable are currently not supported.";
561  }
563  AppendToOverflowValidator(arg.start(), &for_overflow_validation);
564  if (arg.size().vars_size() > 1) {
565  return "Interval with a size expression containing more than one "
566  "variable are currently not supported.";
567  }
569  if (ct.enforcement_literal().empty() &&
570  MinOfExpression(model, arg.size()) < 0) {
571  return absl::StrCat(
572  "The size of an performed interval must be >= 0 in constraint: ",
574  }
575  AppendToOverflowValidator(arg.size(), &for_overflow_validation);
576  if (arg.end().vars_size() > 1) {
577  return "Interval with a end expression containing more than one "
578  "variable are currently not supported.";
579  }
581  AppendToOverflowValidator(arg.end(), &for_overflow_validation);
582 
583  if (PossibleIntegerOverflow(model, for_overflow_validation.vars(),
584  for_overflow_validation.coeffs(),
585  for_overflow_validation.offset())) {
586  return absl::StrCat("Possible overflow in interval: ",
587  ProtobufShortDebugString(ct.interval()));
588  }
589 
590  return "";
591 }
592 
593 std::string ValidateCumulativeConstraint(const CpModelProto& model,
594  const ConstraintProto& ct) {
595  if (ct.cumulative().intervals_size() != ct.cumulative().demands_size()) {
596  return absl::StrCat("intervals_size() != demands_size() in constraint: ",
598  }
599 
601  ValidateLinearExpression(model, ct.cumulative().capacity()));
602  for (const LinearExpressionProto& demand : ct.cumulative().demands()) {
604  }
605 
606  for (const LinearExpressionProto& demand_expr : ct.cumulative().demands()) {
607  if (MinOfExpression(model, demand_expr) < 0) {
608  return absl::StrCat(
609  "Demand ", ProtobufDebugString(demand_expr),
610  " must be positive in constraint: ", ProtobufDebugString(ct));
611  }
612  if (demand_expr.vars_size() > 1) {
613  return absl::StrCat("Demand ", ProtobufDebugString(demand_expr),
614  " must be affine or constant in constraint: ",
616  }
617  }
618  if (ct.cumulative().capacity().vars_size() > 1) {
619  return absl::StrCat(
620  "capacity ", ProtobufDebugString(ct.cumulative().capacity()),
621  " must be affine or constant in constraint: ", ProtobufDebugString(ct));
622  }
623 
624  int64_t sum_max_demands = 0;
625  for (const LinearExpressionProto& demand_expr : ct.cumulative().demands()) {
626  const int64_t demand_max = MaxOfExpression(model, demand_expr);
627  DCHECK_GE(demand_max, 0);
628  sum_max_demands = CapAdd(sum_max_demands, demand_max);
629  if (sum_max_demands == std::numeric_limits<int64_t>::max()) {
630  return "The sum of max demands do not fit on an int64_t 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().time_exprs().size() !=
670  ct.reservoir().level_changes().size()) {
671  return absl::StrCat(
672  "time_exprs and level_changes fields must be of the same size: ",
674  }
675  for (const LinearExpressionProto& expr : ct.reservoir().time_exprs()) {
676  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, expr));
677  }
678  for (const LinearExpressionProto& expr : ct.reservoir().level_changes()) {
679  RETURN_IF_NOT_EMPTY(ValidateConstantAffineExpression(model, expr));
680  }
681  if (ct.reservoir().min_level() > 0) {
682  return absl::StrCat(
683  "The min level of a reservoir must be <= 0. Please use fixed events to "
684  "setup initial state: ",
686  }
687  if (ct.reservoir().max_level() < 0) {
688  return absl::StrCat(
689  "The max level of a reservoir must be >= 0. Please use fixed events to "
690  "setup initial state: ",
692  }
693 
694  int64_t sum_abs = 0;
695  for (const LinearExpressionProto& demand : ct.reservoir().level_changes()) {
696  // We test for min int64_t before the abs().
697  const int64_t demand_min = MinOfExpression(model, demand);
698  const int64_t demand_max = MaxOfExpression(model, demand);
699  sum_abs = CapAdd(sum_abs, std::max(CapAbs(demand_min), CapAbs(demand_max)));
700  if (sum_abs == std::numeric_limits<int64_t>::max()) {
701  return "Possible integer overflow in constraint: " +
703  }
704  }
705  if (ct.reservoir().active_literals_size() > 0 &&
706  ct.reservoir().active_literals_size() !=
707  ct.reservoir().time_exprs_size()) {
708  return "Wrong array length of active_literals variables";
709  }
710  if (ct.reservoir().level_changes_size() > 0 &&
711  ct.reservoir().level_changes_size() != ct.reservoir().time_exprs_size()) {
712  return "Wrong array length of level_changes variables";
713  }
714  return "";
715 }
716 
717 std::string ValidateObjective(const CpModelProto& model,
718  const CpObjectiveProto& obj) {
719  if (!DomainInProtoIsValid(obj)) {
720  return absl::StrCat("The objective has and invalid domain() format: ",
722  }
723  if (obj.vars().size() != obj.coeffs().size()) {
724  return absl::StrCat("vars and coeffs size do not match in objective: ",
726  }
727  for (const int v : obj.vars()) {
728  if (!VariableReferenceIsValid(model, v)) {
729  return absl::StrCat("Out of bound integer variable ", v,
730  " in objective: ", ProtobufShortDebugString(obj));
731  }
732  }
733  if (PossibleIntegerOverflow(model, obj.vars(), obj.coeffs())) {
734  return "Possible integer overflow in objective: " +
735  ProtobufDebugString(obj);
736  }
737  return "";
738 }
739 
740 std::string ValidateFloatingPointObjective(double max_valid_magnitude,
741  const CpModelProto& model,
742  const FloatObjectiveProto& obj) {
743  if (obj.vars().size() != obj.coeffs().size()) {
744  return absl::StrCat("vars and coeffs size do not match in objective: ",
746  }
747  for (const int v : obj.vars()) {
748  if (!VariableIndexIsValid(model, v)) {
749  return absl::StrCat("Out of bound integer variable ", v,
750  " in objective: ", ProtobufShortDebugString(obj));
751  }
752  }
753  for (const double coeff : obj.coeffs()) {
754  if (!std::isfinite(coeff)) {
755  return absl::StrCat("Coefficients must be finite in objective: ",
757  }
758  if (std::abs(coeff) > max_valid_magnitude) {
759  return absl::StrCat(
760  "Coefficients larger than params.mip_max_valid_magnitude() [value = ",
761  max_valid_magnitude,
762  "] in objective: ", ProtobufShortDebugString(obj));
763  }
764  }
765  if (!std::isfinite(obj.offset())) {
766  return absl::StrCat("Offset must be finite in objective: ",
768  }
769  return "";
770 }
771 
772 std::string ValidateSearchStrategies(const CpModelProto& model) {
773  for (const DecisionStrategyProto& strategy : model.search_strategy()) {
774  const int vss = strategy.variable_selection_strategy();
775  if (vss != DecisionStrategyProto::CHOOSE_FIRST &&
776  vss != DecisionStrategyProto::CHOOSE_LOWEST_MIN &&
777  vss != DecisionStrategyProto::CHOOSE_HIGHEST_MAX &&
778  vss != DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE &&
779  vss != DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE) {
780  return absl::StrCat(
781  "Unknown or unsupported variable_selection_strategy: ", vss);
782  }
783  const int drs = strategy.domain_reduction_strategy();
784  if (drs != DecisionStrategyProto::SELECT_MIN_VALUE &&
785  drs != DecisionStrategyProto::SELECT_MAX_VALUE &&
786  drs != DecisionStrategyProto::SELECT_LOWER_HALF &&
787  drs != DecisionStrategyProto::SELECT_UPPER_HALF &&
788  drs != DecisionStrategyProto::SELECT_MEDIAN_VALUE) {
789  return absl::StrCat("Unknown or unsupported domain_reduction_strategy: ",
790  drs);
791  }
792  for (const int ref : strategy.variables()) {
793  if (!VariableReferenceIsValid(model, ref)) {
794  return absl::StrCat("Invalid variable reference in strategy: ",
795  ProtobufShortDebugString(strategy));
796  }
797  if (drs == DecisionStrategyProto::SELECT_MEDIAN_VALUE &&
798  ReadDomainFromProto(model.variables(PositiveRef(ref))).Size() >
799  100000) {
800  return absl::StrCat("Variable #", PositiveRef(ref),
801  " has a domain too large to be used in a"
802  " SELECT_MEDIAN_VALUE value selection strategy");
803  }
804  }
805  int previous_index = -1;
806  for (const auto& transformation : strategy.transformations()) {
807  if (transformation.positive_coeff() <= 0) {
808  return absl::StrCat("Affine transformation coeff should be positive: ",
809  ProtobufShortDebugString(transformation));
810  }
811  if (transformation.index() <= previous_index ||
812  transformation.index() >= strategy.variables_size()) {
813  return absl::StrCat(
814  "Invalid indices (must be sorted and valid) in transformation: ",
815  ProtobufShortDebugString(transformation));
816  }
817  previous_index = transformation.index();
818  }
819  }
820  return "";
821 }
822 
823 std::string ValidateSolutionHint(const CpModelProto& model) {
824  if (!model.has_solution_hint()) return "";
825  const auto& hint = model.solution_hint();
826  if (hint.vars().size() != hint.values().size()) {
827  return "Invalid solution hint: vars and values do not have the same size.";
828  }
829  for (const int ref : hint.vars()) {
830  if (!VariableReferenceIsValid(model, ref)) {
831  return absl::StrCat("Invalid variable reference in solution hint: ", ref);
832  }
833  }
834 
835  // Reject hints with duplicate variables as this is likely a user error.
836  absl::flat_hash_set<int> indices;
837  for (const int var : hint.vars()) {
838  const auto insert = indices.insert(PositiveRef(var));
839  if (!insert.second) {
840  return absl::StrCat(
841  "The solution hint contains duplicate variables like the variable "
842  "with index #",
843  PositiveRef(var));
844  }
845  }
846 
847  // Reject hints equals to INT_MIN or INT_MAX.
848  for (const int64_t value : hint.values()) {
851  return "The solution hint cannot contains the INT_MIN or INT_MAX values.";
852  }
853  }
854 
855  return "";
856 }
857 
858 } // namespace
859 
860 bool PossibleIntegerOverflow(const CpModelProto& model,
861  absl::Span<const int> vars,
862  absl::Span<const int64_t> coeffs, int64_t offset) {
863  if (offset == std::numeric_limits<int64_t>::min()) return true;
864  int64_t sum_min = -std::abs(offset);
865  int64_t sum_max = +std::abs(offset);
866  for (int i = 0; i < vars.size(); ++i) {
867  const int ref = vars[i];
868  const auto& var_proto = model.variables(PositiveRef(ref));
869  const int64_t min_domain = var_proto.domain(0);
870  const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
871  if (coeffs[i] == std::numeric_limits<int64_t>::min()) return true;
872  const int64_t coeff = RefIsPositive(ref) ? coeffs[i] : -coeffs[i];
873  const int64_t prod1 = CapProd(min_domain, coeff);
874  const int64_t prod2 = CapProd(max_domain, coeff);
875 
876  // Note that we use min/max with zero to disallow "alternative" terms and
877  // be sure that we cannot have an overflow if we do the computation in a
878  // different order.
879  sum_min = CapAdd(sum_min, std::min(int64_t{0}, std::min(prod1, prod2)));
880  sum_max = CapAdd(sum_max, std::max(int64_t{0}, std::max(prod1, prod2)));
881  for (const int64_t v : {prod1, prod2, sum_min, sum_max}) {
884  return true;
885  }
886  }
887 
888  // In addition to computing the min/max possible sum, we also often compare
889  // it with the constraint bounds, so we do not want max - min to overflow.
890  // We might also create an intermediate variable to represent the sum. It
891  if (sum_min < std::numeric_limits<int64_t>::min() / 2) return true;
892  if (sum_max > std::numeric_limits<int64_t>::max() / 2) return true;
893  return false;
894 }
895 
896 std::string ValidateCpModel(const CpModelProto& model, bool after_presolve) {
897  for (int v = 0; v < model.variables_size(); ++v) {
898  RETURN_IF_NOT_EMPTY(ValidateIntegerVariable(model, v));
899  }
900 
901  // We need to validate the intervals used first, so we add these constraints
902  // here so that we can validate them in a second pass.
903  std::vector<int> constraints_using_intervals;
904 
905  for (int c = 0; c < model.constraints_size(); ++c) {
906  RETURN_IF_NOT_EMPTY(ValidateVariablesUsedInConstraint(model, c));
907 
908  // By default, a constraint does not support enforcement literals except if
909  // explicitly stated by setting this to true below.
910  bool support_enforcement = false;
911 
912  // Other non-generic validations.
913  const ConstraintProto& ct = model.constraints(c);
914  switch (ct.constraint_case()) {
915  case ConstraintProto::ConstraintCase::kBoolOr:
916  support_enforcement = true;
917  break;
918  case ConstraintProto::ConstraintCase::kBoolAnd:
919  support_enforcement = true;
920  break;
921  case ConstraintProto::ConstraintCase::kLinear:
922  support_enforcement = true;
923  RETURN_IF_NOT_EMPTY(ValidateLinearConstraint(model, ct));
924  break;
925  case ConstraintProto::ConstraintCase::kLinMax: {
927  ValidateLinearExpression(model, ct.lin_max().target()));
928  for (const LinearExpressionProto& expr : ct.lin_max().exprs()) {
930  }
931  break;
932  }
933  case ConstraintProto::ConstraintCase::kIntProd:
934  RETURN_IF_NOT_EMPTY(ValidateIntProdConstraint(model, ct));
935  break;
936  case ConstraintProto::ConstraintCase::kIntDiv:
937  RETURN_IF_NOT_EMPTY(ValidateIntDivConstraint(model, ct));
938  break;
939  case ConstraintProto::ConstraintCase::kIntMod:
940  RETURN_IF_NOT_EMPTY(ValidateIntModConstraint(model, ct));
941  break;
942  case ConstraintProto::ConstraintCase::kInverse:
943  if (ct.inverse().f_direct().size() != ct.inverse().f_inverse().size()) {
944  return absl::StrCat("Non-matching fields size in inverse: ",
946  }
947  break;
948  case ConstraintProto::ConstraintCase::kAllDiff:
949  for (const LinearExpressionProto& expr : ct.all_diff().exprs()) {
950  RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, expr));
951  }
952  break;
953  case ConstraintProto::ConstraintCase::kElement:
954  RETURN_IF_NOT_EMPTY(ValidateElementConstraint(model, ct));
955  break;
956  case ConstraintProto::ConstraintCase::kTable:
957  RETURN_IF_NOT_EMPTY(ValidateTableConstraint(model, ct));
958  break;
959  case ConstraintProto::ConstraintCase::kAutomaton:
960  RETURN_IF_NOT_EMPTY(ValidateAutomatonConstraint(model, ct));
961  break;
962  case ConstraintProto::ConstraintCase::kCircuit:
964  ValidateGraphInput(/*is_route=*/false, model, ct.circuit()));
965  break;
966  case ConstraintProto::ConstraintCase::kRoutes:
967  RETURN_IF_NOT_EMPTY(ValidateRoutesConstraint(model, ct));
968  break;
969  case ConstraintProto::ConstraintCase::kInterval:
970  RETURN_IF_NOT_EMPTY(ValidateIntervalConstraint(model, ct));
971  support_enforcement = true;
972  break;
973  case ConstraintProto::ConstraintCase::kCumulative:
974  constraints_using_intervals.push_back(c);
975  break;
976  case ConstraintProto::ConstraintCase::kNoOverlap:
977  constraints_using_intervals.push_back(c);
978  break;
979  case ConstraintProto::ConstraintCase::kNoOverlap2D:
980  constraints_using_intervals.push_back(c);
981  break;
982  case ConstraintProto::ConstraintCase::kReservoir:
983  RETURN_IF_NOT_EMPTY(ValidateReservoirConstraint(model, ct));
984  break;
985  case ConstraintProto::ConstraintCase::kDummyConstraint:
986  return "The dummy constraint should never appear in a model.";
987  default:
988  break;
989  }
990 
991  // Because some client set fixed enforcement literal which are supported
992  // in the presolve for all constraints, we just check that there is no
993  // non-fixed enforcement.
994  if (!support_enforcement && !ct.enforcement_literal().empty()) {
995  for (const int ref : ct.enforcement_literal()) {
996  const int var = PositiveRef(ref);
997  const Domain domain = ReadDomainFromProto(model.variables(var));
998  if (domain.Size() != 1) {
999  return absl::StrCat(
1000  "Enforcement literal not supported in constraint: ",
1002  }
1003  }
1004  }
1005  }
1006 
1007  // Extra validation for constraint using intervals.
1008  for (const int c : constraints_using_intervals) {
1010  ValidateIntervalsUsedInConstraint(after_presolve, model, c));
1011 
1012  const ConstraintProto& ct = model.constraints(c);
1013  switch (ct.constraint_case()) {
1014  case ConstraintProto::ConstraintCase::kCumulative:
1015  RETURN_IF_NOT_EMPTY(ValidateCumulativeConstraint(model, ct));
1016  break;
1017  case ConstraintProto::ConstraintCase::kNoOverlap:
1018  break;
1019  case ConstraintProto::ConstraintCase::kNoOverlap2D:
1020  RETURN_IF_NOT_EMPTY(ValidateNoOverlap2DConstraint(model, ct));
1021  break;
1022  default:
1023  LOG(DFATAL) << "Shouldn't be here";
1024  }
1025  }
1026 
1027  if (model.has_objective() && model.has_floating_point_objective()) {
1028  return "A model cannot have both an objective and a floating point "
1029  "objective.";
1030  }
1031  if (model.has_objective()) {
1032  RETURN_IF_NOT_EMPTY(ValidateObjective(model, model.objective()));
1033 
1034  if (model.objective().integer_scaling_factor() != 0 ||
1035  model.objective().integer_before_offset() != 0 ||
1036  model.objective().integer_after_offset() != 0) {
1037  // If any of these fields are set, the domain must be set.
1038  if (model.objective().domain().empty()) {
1039  return absl::StrCat(
1040  "Objective integer scaling or offset is set without an objective "
1041  "domain.");
1042  }
1043 
1044  // Check that we can transform any value in the objective domain without
1045  // overflow. We only check the bounds which is enough.
1046  bool overflow = false;
1047  for (const int64_t v : model.objective().domain()) {
1048  int64_t t = CapAdd(v, model.objective().integer_before_offset());
1049  if (AtMinOrMaxInt64(t)) {
1050  overflow = true;
1051  break;
1052  }
1053  t = CapProd(t, model.objective().integer_scaling_factor());
1054  if (AtMinOrMaxInt64(t)) {
1055  overflow = true;
1056  break;
1057  }
1058  t = CapAdd(t, model.objective().integer_after_offset());
1059  if (AtMinOrMaxInt64(t)) {
1060  overflow = true;
1061  break;
1062  }
1063  }
1064  if (overflow) {
1065  return absl::StrCat(
1066  "Internal fields related to the postsolve of the integer objective "
1067  "are causing a potential integer overflow: ",
1068  ProtobufShortDebugString(model.objective()));
1069  }
1070  }
1071  }
1072  RETURN_IF_NOT_EMPTY(ValidateSearchStrategies(model));
1073  RETURN_IF_NOT_EMPTY(ValidateSolutionHint(model));
1074  for (const int ref : model.assumptions()) {
1075  if (!LiteralReferenceIsValid(model, ref)) {
1076  return absl::StrCat("Invalid literal reference ", ref,
1077  " in the 'assumptions' field.");
1078  }
1079  }
1080  return "";
1081 }
1082 
1083 std::string ValidateInputCpModel(const SatParameters& params,
1084  const CpModelProto& model) {
1086  if (model.has_floating_point_objective()) {
1088  ValidateFloatingPointObjective(params.mip_max_valid_magnitude(), model,
1089  model.floating_point_objective()));
1090  }
1091  return "";
1092 }
1093 
1094 #undef RETURN_IF_NOT_EMPTY
1095 
1096 // =============================================================================
1097 // Solution Feasibility.
1098 // =============================================================================
1099 
1100 namespace {
1101 
1102 class ConstraintChecker {
1103  public:
1104  explicit ConstraintChecker(absl::Span<const int64_t> variable_values)
1105  : variable_values_(variable_values.begin(), variable_values.end()) {}
1106 
1107  bool LiteralIsTrue(int l) const {
1108  if (l >= 0) return variable_values_[l] != 0;
1109  return variable_values_[-l - 1] == 0;
1110  }
1111 
1112  bool LiteralIsFalse(int l) const { return !LiteralIsTrue(l); }
1113 
1114  int64_t Value(int var) const {
1115  if (var >= 0) return variable_values_[var];
1116  return -variable_values_[-var - 1];
1117  }
1118 
1119  bool ConstraintIsEnforced(const ConstraintProto& ct) {
1120  for (const int lit : ct.enforcement_literal()) {
1121  if (LiteralIsFalse(lit)) return false;
1122  }
1123  return true;
1124  }
1125 
1126  bool BoolOrConstraintIsFeasible(const ConstraintProto& ct) {
1127  for (const int lit : ct.bool_or().literals()) {
1128  if (LiteralIsTrue(lit)) return true;
1129  }
1130  return false;
1131  }
1132 
1133  bool BoolAndConstraintIsFeasible(const ConstraintProto& ct) {
1134  for (const int lit : ct.bool_and().literals()) {
1135  if (LiteralIsFalse(lit)) return false;
1136  }
1137  return true;
1138  }
1139 
1140  bool AtMostOneConstraintIsFeasible(const ConstraintProto& ct) {
1141  int num_true_literals = 0;
1142  for (const int lit : ct.at_most_one().literals()) {
1143  if (LiteralIsTrue(lit)) ++num_true_literals;
1144  }
1145  return num_true_literals <= 1;
1146  }
1147 
1148  bool ExactlyOneConstraintIsFeasible(const ConstraintProto& ct) {
1149  int num_true_literals = 0;
1150  for (const int lit : ct.exactly_one().literals()) {
1151  if (LiteralIsTrue(lit)) ++num_true_literals;
1152  }
1153  return num_true_literals == 1;
1154  }
1155 
1156  bool BoolXorConstraintIsFeasible(const ConstraintProto& ct) {
1157  int sum = 0;
1158  for (const int lit : ct.bool_xor().literals()) {
1159  sum ^= LiteralIsTrue(lit) ? 1 : 0;
1160  }
1161  return sum == 1;
1162  }
1163 
1164  bool LinearConstraintIsFeasible(const ConstraintProto& ct) {
1165  int64_t sum = 0;
1166  const int num_variables = ct.linear().coeffs_size();
1167  for (int i = 0; i < num_variables; ++i) {
1168  sum += Value(ct.linear().vars(i)) * ct.linear().coeffs(i);
1169  }
1170  const bool result = DomainInProtoContains(ct.linear(), sum);
1171  if (!result) {
1172  VLOG(1) << "Activity: " << sum;
1173  }
1174  return result;
1175  }
1176 
1177  int64_t LinearExpressionValue(const LinearExpressionProto& expr) const {
1178  int64_t sum = expr.offset();
1179  const int num_variables = expr.vars_size();
1180  for (int i = 0; i < num_variables; ++i) {
1181  sum += Value(expr.vars(i)) * expr.coeffs(i);
1182  }
1183  return sum;
1184  }
1185 
1186  bool LinMaxConstraintIsFeasible(const ConstraintProto& ct) {
1187  const int64_t max = LinearExpressionValue(ct.lin_max().target());
1188  int64_t actual_max = std::numeric_limits<int64_t>::min();
1189  for (int i = 0; i < ct.lin_max().exprs_size(); ++i) {
1190  const int64_t expr_value = LinearExpressionValue(ct.lin_max().exprs(i));
1191  actual_max = std::max(actual_max, expr_value);
1192  }
1193  return max == actual_max;
1194  }
1195 
1196  bool IntProdConstraintIsFeasible(const ConstraintProto& ct) {
1197  const int64_t prod = LinearExpressionValue(ct.int_prod().target());
1198  int64_t actual_prod = 1;
1199  for (const LinearExpressionProto& expr : ct.int_prod().exprs()) {
1200  actual_prod = CapProd(actual_prod, LinearExpressionValue(expr));
1201  }
1202  return prod == actual_prod;
1203  }
1204 
1205  bool IntDivConstraintIsFeasible(const ConstraintProto& ct) {
1206  return LinearExpressionValue(ct.int_div().target()) ==
1207  LinearExpressionValue(ct.int_div().exprs(0)) /
1208  LinearExpressionValue(ct.int_div().exprs(1));
1209  }
1210 
1211  bool IntModConstraintIsFeasible(const ConstraintProto& ct) {
1212  return LinearExpressionValue(ct.int_mod().target()) ==
1213  LinearExpressionValue(ct.int_mod().exprs(0)) %
1214  LinearExpressionValue(ct.int_mod().exprs(1));
1215  }
1216 
1217  bool AllDiffConstraintIsFeasible(const ConstraintProto& ct) {
1218  absl::flat_hash_set<int64_t> values;
1219  for (const LinearExpressionProto& expr : ct.all_diff().exprs()) {
1220  const int64_t value = LinearExpressionValue(expr);
1221  const auto [it, inserted] = values.insert(value);
1222  if (!inserted) return false;
1223  }
1224  return true;
1225  }
1226 
1227  int64_t IntervalStart(const IntervalConstraintProto& interval) const {
1228  return LinearExpressionValue(interval.start());
1229  }
1230 
1231  int64_t IntervalSize(const IntervalConstraintProto& interval) const {
1232  return LinearExpressionValue(interval.size());
1233  }
1234 
1235  int64_t IntervalEnd(const IntervalConstraintProto& interval) const {
1236  return LinearExpressionValue(interval.end());
1237  }
1238 
1239  bool IntervalConstraintIsFeasible(const ConstraintProto& ct) {
1240  const int64_t size = IntervalSize(ct.interval());
1241  if (size < 0) return false;
1242  return IntervalStart(ct.interval()) + size == IntervalEnd(ct.interval());
1243  }
1244 
1245  bool NoOverlapConstraintIsFeasible(const CpModelProto& model,
1246  const ConstraintProto& ct) {
1247  std::vector<std::pair<int64_t, int64_t>> start_durations_pairs;
1248  for (const int i : ct.no_overlap().intervals()) {
1249  const ConstraintProto& interval_constraint = model.constraints(i);
1250  if (ConstraintIsEnforced(interval_constraint)) {
1251  const IntervalConstraintProto& interval =
1252  interval_constraint.interval();
1253  start_durations_pairs.push_back(
1254  {IntervalStart(interval), IntervalSize(interval)});
1255  }
1256  }
1257  std::sort(start_durations_pairs.begin(), start_durations_pairs.end());
1258  int64_t previous_end = std::numeric_limits<int64_t>::min();
1259  for (const auto& pair : start_durations_pairs) {
1260  if (pair.first < previous_end) return false;
1261  previous_end = pair.first + pair.second;
1262  }
1263  return true;
1264  }
1265 
1266  bool IntervalsAreDisjoint(const IntervalConstraintProto& interval1,
1267  const IntervalConstraintProto& interval2) {
1268  return IntervalEnd(interval1) <= IntervalStart(interval2) ||
1269  IntervalEnd(interval2) <= IntervalStart(interval1);
1270  }
1271 
1272  bool IntervalIsEmpty(const IntervalConstraintProto& interval) {
1273  return IntervalStart(interval) == IntervalEnd(interval);
1274  }
1275 
1276  bool NoOverlap2DConstraintIsFeasible(const CpModelProto& model,
1277  const ConstraintProto& ct) {
1278  const auto& arg = ct.no_overlap_2d();
1279  // Those intervals from arg.x_intervals and arg.y_intervals where both
1280  // the x and y intervals are enforced.
1281  std::vector<std::pair<const IntervalConstraintProto* const,
1282  const IntervalConstraintProto* const>>
1283  enforced_intervals_xy;
1284  {
1285  const int num_intervals = arg.x_intervals_size();
1286  CHECK_EQ(arg.y_intervals_size(), num_intervals);
1287  for (int i = 0; i < num_intervals; ++i) {
1288  const ConstraintProto& x = model.constraints(arg.x_intervals(i));
1289  const ConstraintProto& y = model.constraints(arg.y_intervals(i));
1290  if (ConstraintIsEnforced(x) && ConstraintIsEnforced(y) &&
1291  (!arg.boxes_with_null_area_can_overlap() ||
1292  (!IntervalIsEmpty(x.interval()) &&
1293  !IntervalIsEmpty(y.interval())))) {
1294  enforced_intervals_xy.push_back({&x.interval(), &y.interval()});
1295  }
1296  }
1297  }
1298  const int num_enforced_intervals = enforced_intervals_xy.size();
1299  for (int i = 0; i < num_enforced_intervals; ++i) {
1300  for (int j = i + 1; j < num_enforced_intervals; ++j) {
1301  const auto& xi = *enforced_intervals_xy[i].first;
1302  const auto& yi = *enforced_intervals_xy[i].second;
1303  const auto& xj = *enforced_intervals_xy[j].first;
1304  const auto& yj = *enforced_intervals_xy[j].second;
1305  if (!IntervalsAreDisjoint(xi, xj) && !IntervalsAreDisjoint(yi, yj) &&
1306  !IntervalIsEmpty(xi) && !IntervalIsEmpty(xj) &&
1307  !IntervalIsEmpty(yi) && !IntervalIsEmpty(yj)) {
1308  VLOG(1) << "Interval " << i << "(x=[" << IntervalStart(xi) << ", "
1309  << IntervalEnd(xi) << "], y=[" << IntervalStart(yi) << ", "
1310  << IntervalEnd(yi) << "]) and " << j << "(x=["
1311  << IntervalStart(xj) << ", " << IntervalEnd(xj) << "], y=["
1312  << IntervalStart(yj) << ", " << IntervalEnd(yj)
1313  << "]) are not disjoint.";
1314  return false;
1315  }
1316  }
1317  }
1318  return true;
1319  }
1320 
1321  bool CumulativeConstraintIsFeasible(const CpModelProto& model,
1322  const ConstraintProto& ct) {
1323  // TODO(user): Improve complexity for large durations.
1324  const int64_t capacity = LinearExpressionValue(ct.cumulative().capacity());
1325  const int num_intervals = ct.cumulative().intervals_size();
1326  absl::flat_hash_map<int64_t, int64_t> usage;
1327  for (int i = 0; i < num_intervals; ++i) {
1328  const ConstraintProto& interval_constraint =
1329  model.constraints(ct.cumulative().intervals(i));
1330  if (ConstraintIsEnforced(interval_constraint)) {
1331  const IntervalConstraintProto& interval =
1332  interval_constraint.interval();
1333  const int64_t start = IntervalStart(interval);
1334  const int64_t duration = IntervalSize(interval);
1335  const int64_t demand =
1336  LinearExpressionValue(ct.cumulative().demands(i));
1337  for (int64_t t = start; t < start + duration; ++t) {
1338  usage[t] += demand;
1339  if (usage[t] > capacity) {
1340  VLOG(1) << "time: " << t << " usage: " << usage[t]
1341  << " capa: " << capacity;
1342  return false;
1343  }
1344  }
1345  }
1346  }
1347  return true;
1348  }
1349 
1350  bool ElementConstraintIsFeasible(const ConstraintProto& ct) {
1351  if (ct.element().vars().empty()) return false;
1352  const int index = Value(ct.element().index());
1353  if (index < 0 || index >= ct.element().vars_size()) return false;
1354  return Value(ct.element().vars(index)) == Value(ct.element().target());
1355  }
1356 
1357  bool TableConstraintIsFeasible(const ConstraintProto& ct) {
1358  const int size = ct.table().vars_size();
1359  if (size == 0) return true;
1360  for (int row_start = 0; row_start < ct.table().values_size();
1361  row_start += size) {
1362  int i = 0;
1363  while (Value(ct.table().vars(i)) == ct.table().values(row_start + i)) {
1364  ++i;
1365  if (i == size) return !ct.table().negated();
1366  }
1367  }
1368  return ct.table().negated();
1369  }
1370 
1371  bool AutomatonConstraintIsFeasible(const ConstraintProto& ct) {
1372  // Build the transition table {tail, label} -> head.
1373  absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> transition_map;
1374  const int num_transitions = ct.automaton().transition_tail().size();
1375  for (int i = 0; i < num_transitions; ++i) {
1376  transition_map[{ct.automaton().transition_tail(i),
1377  ct.automaton().transition_label(i)}] =
1378  ct.automaton().transition_head(i);
1379  }
1380 
1381  // Walk the automaton.
1382  int64_t current_state = ct.automaton().starting_state();
1383  const int num_steps = ct.automaton().vars_size();
1384  for (int i = 0; i < num_steps; ++i) {
1385  const std::pair<int64_t, int64_t> key = {current_state,
1386  Value(ct.automaton().vars(i))};
1387  if (!transition_map.contains(key)) {
1388  return false;
1389  }
1390  current_state = transition_map[key];
1391  }
1392 
1393  // Check we are now in a final state.
1394  for (const int64_t final : ct.automaton().final_states()) {
1395  if (current_state == final) return true;
1396  }
1397  return false;
1398  }
1399 
1400  bool CircuitConstraintIsFeasible(const ConstraintProto& ct) {
1401  // Compute the set of relevant nodes for the constraint and set the next of
1402  // each of them. This also detects duplicate nexts.
1403  const int num_arcs = ct.circuit().tails_size();
1404  absl::flat_hash_set<int> nodes;
1405  absl::flat_hash_map<int, int> nexts;
1406  for (int i = 0; i < num_arcs; ++i) {
1407  const int tail = ct.circuit().tails(i);
1408  const int head = ct.circuit().heads(i);
1409  nodes.insert(tail);
1410  nodes.insert(head);
1411  if (LiteralIsFalse(ct.circuit().literals(i))) continue;
1412  if (nexts.contains(tail)) {
1413  VLOG(1) << "Node with two outgoing arcs";
1414  return false; // Duplicate.
1415  }
1416  nexts[tail] = head;
1417  }
1418 
1419  // All node must have a next.
1420  int in_cycle;
1421  int cycle_size = 0;
1422  for (const int node : nodes) {
1423  if (!nexts.contains(node)) {
1424  VLOG(1) << "Node with no next: " << node;
1425  return false; // No next.
1426  }
1427  if (nexts[node] == node) continue; // skip self-loop.
1428  in_cycle = node;
1429  ++cycle_size;
1430  }
1431  if (cycle_size == 0) return true;
1432 
1433  // Check that we have only one cycle. visited is used to not loop forever if
1434  // we have a "rho" shape instead of a cycle.
1435  absl::flat_hash_set<int> visited;
1436  int current = in_cycle;
1437  int num_visited = 0;
1438  while (!visited.contains(current)) {
1439  ++num_visited;
1440  visited.insert(current);
1441  current = nexts[current];
1442  }
1443  if (current != in_cycle) {
1444  VLOG(1) << "Rho shape";
1445  return false; // Rho shape.
1446  }
1447  if (num_visited != cycle_size) {
1448  VLOG(1) << "More than one cycle";
1449  }
1450  return num_visited == cycle_size; // Another cycle somewhere if false.
1451  }
1452 
1453  bool RoutesConstraintIsFeasible(const ConstraintProto& ct) {
1454  const int num_arcs = ct.routes().tails_size();
1455  int num_used_arcs = 0;
1456  int num_self_arcs = 0;
1457  int num_nodes = 0;
1458  std::vector<int> tail_to_head;
1459  std::vector<int> depot_nexts;
1460  for (int i = 0; i < num_arcs; ++i) {
1461  const int tail = ct.routes().tails(i);
1462  const int head = ct.routes().heads(i);
1463  num_nodes = std::max(num_nodes, 1 + tail);
1464  num_nodes = std::max(num_nodes, 1 + head);
1465  tail_to_head.resize(num_nodes, -1);
1466  if (LiteralIsTrue(ct.routes().literals(i))) {
1467  if (tail == head) {
1468  if (tail == 0) return false;
1469  ++num_self_arcs;
1470  continue;
1471  }
1472  ++num_used_arcs;
1473  if (tail == 0) {
1474  depot_nexts.push_back(head);
1475  } else {
1476  if (tail_to_head[tail] != -1) return false;
1477  tail_to_head[tail] = head;
1478  }
1479  }
1480  }
1481 
1482  // An empty constraint with no node to visit should be feasible.
1483  if (num_nodes == 0) return true;
1484 
1485  // Make sure each routes from the depot go back to it, and count such arcs.
1486  int count = 0;
1487  for (int start : depot_nexts) {
1488  ++count;
1489  while (start != 0) {
1490  if (tail_to_head[start] == -1) return false;
1491  start = tail_to_head[start];
1492  ++count;
1493  }
1494  }
1495 
1496  if (count != num_used_arcs) {
1497  VLOG(1) << "count: " << count << " != num_used_arcs:" << num_used_arcs;
1498  return false;
1499  }
1500 
1501  // Each routes cover as many node as there is arcs, but this way we count
1502  // multiple time_exprs the depot. So the number of nodes covered are:
1503  // count - depot_nexts.size() + 1.
1504  // And this number + the self arcs should be num_nodes.
1505  if (count - depot_nexts.size() + 1 + num_self_arcs != num_nodes) {
1506  VLOG(1) << "Not all nodes are covered!";
1507  return false;
1508  }
1509 
1510  return true;
1511  }
1512 
1513  bool InverseConstraintIsFeasible(const ConstraintProto& ct) {
1514  const int num_variables = ct.inverse().f_direct_size();
1515  if (num_variables != ct.inverse().f_inverse_size()) return false;
1516  // Check that f_inverse(f_direct(i)) == i; this is sufficient.
1517  for (int i = 0; i < num_variables; i++) {
1518  const int fi = Value(ct.inverse().f_direct(i));
1519  if (fi < 0 || num_variables <= fi) return false;
1520  if (i != Value(ct.inverse().f_inverse(fi))) return false;
1521  }
1522  return true;
1523  }
1524 
1525  bool ReservoirConstraintIsFeasible(const ConstraintProto& ct) {
1526  const int num_variables = ct.reservoir().time_exprs_size();
1527  const int64_t min_level = ct.reservoir().min_level();
1528  const int64_t max_level = ct.reservoir().max_level();
1529  absl::btree_map<int64_t, int64_t> deltas;
1530  const bool has_active_variables = ct.reservoir().active_literals_size() > 0;
1531  for (int i = 0; i < num_variables; i++) {
1532  const int64_t time = LinearExpressionValue(ct.reservoir().time_exprs(i));
1533  if (!has_active_variables ||
1534  Value(ct.reservoir().active_literals(i)) == 1) {
1535  const int64_t level =
1536  LinearExpressionValue(ct.reservoir().level_changes(i));
1537  deltas[time] += level;
1538  }
1539  }
1540  int64_t current_level = 0;
1541  for (const auto& delta : deltas) {
1542  current_level += delta.second;
1543  if (current_level < min_level || current_level > max_level) {
1544  VLOG(1) << "Reservoir level " << current_level
1545  << " is out of bounds at time" << delta.first;
1546  return false;
1547  }
1548  }
1549  return true;
1550  }
1551 
1552  private:
1553  const std::vector<int64_t> variable_values_;
1554 };
1555 
1556 } // namespace
1557 
1558 bool SolutionIsFeasible(const CpModelProto& model,
1559  absl::Span<const int64_t> variable_values,
1560  const CpModelProto* mapping_proto,
1561  const std::vector<int>* postsolve_mapping) {
1562  if (variable_values.size() != model.variables_size()) {
1563  VLOG(1) << "Wrong number of variables (" << variable_values.size()
1564  << ") in the solution vector. It should be "
1565  << model.variables_size() << ".";
1566  return false;
1567  }
1568 
1569  // Check that all values fall in the variable domains.
1570  for (int i = 0; i < model.variables_size(); ++i) {
1571  if (!DomainInProtoContains(model.variables(i), variable_values[i])) {
1572  VLOG(1) << "Variable #" << i << " has value " << variable_values[i]
1573  << " which do not fall in its domain: "
1574  << ProtobufShortDebugString(model.variables(i));
1575  return false;
1576  }
1577  }
1578 
1579  CHECK_EQ(variable_values.size(), model.variables_size());
1580  ConstraintChecker checker(variable_values);
1581 
1582  for (int c = 0; c < model.constraints_size(); ++c) {
1583  const ConstraintProto& ct = model.constraints(c);
1584 
1585  if (!checker.ConstraintIsEnforced(ct)) continue;
1586 
1587  bool is_feasible = true;
1588  const ConstraintProto::ConstraintCase type = ct.constraint_case();
1589  switch (type) {
1590  case ConstraintProto::ConstraintCase::kBoolOr:
1591  is_feasible = checker.BoolOrConstraintIsFeasible(ct);
1592  break;
1593  case ConstraintProto::ConstraintCase::kBoolAnd:
1594  is_feasible = checker.BoolAndConstraintIsFeasible(ct);
1595  break;
1596  case ConstraintProto::ConstraintCase::kAtMostOne:
1597  is_feasible = checker.AtMostOneConstraintIsFeasible(ct);
1598  break;
1599  case ConstraintProto::ConstraintCase::kExactlyOne:
1600  is_feasible = checker.ExactlyOneConstraintIsFeasible(ct);
1601  break;
1602  case ConstraintProto::ConstraintCase::kBoolXor:
1603  is_feasible = checker.BoolXorConstraintIsFeasible(ct);
1604  break;
1605  case ConstraintProto::ConstraintCase::kLinear:
1606  is_feasible = checker.LinearConstraintIsFeasible(ct);
1607  break;
1608  case ConstraintProto::ConstraintCase::kIntProd:
1609  is_feasible = checker.IntProdConstraintIsFeasible(ct);
1610  break;
1611  case ConstraintProto::ConstraintCase::kIntDiv:
1612  is_feasible = checker.IntDivConstraintIsFeasible(ct);
1613  break;
1614  case ConstraintProto::ConstraintCase::kIntMod:
1615  is_feasible = checker.IntModConstraintIsFeasible(ct);
1616  break;
1617  case ConstraintProto::ConstraintCase::kLinMax:
1618  is_feasible = checker.LinMaxConstraintIsFeasible(ct);
1619  break;
1620  case ConstraintProto::ConstraintCase::kAllDiff:
1621  is_feasible = checker.AllDiffConstraintIsFeasible(ct);
1622  break;
1623  case ConstraintProto::ConstraintCase::kInterval:
1624  if (!checker.IntervalConstraintIsFeasible(ct)) {
1625  if (ct.interval().has_start()) {
1626  // Tricky: For simplified presolve, we require that a separate
1627  // constraint is added to the model to enforce the "interval".
1628  // This indicates that such a constraint was not added to the model.
1629  // It should probably be a validation error, but it is hard to
1630  // detect beforehand.
1631  LOG(ERROR) << "Warning, an interval constraint was likely used "
1632  "without a corresponding linear constraint linking "
1633  "its start, size and end.";
1634  } else {
1635  is_feasible = false;
1636  }
1637  }
1638  break;
1639  case ConstraintProto::ConstraintCase::kNoOverlap:
1640  is_feasible = checker.NoOverlapConstraintIsFeasible(model, ct);
1641  break;
1642  case ConstraintProto::ConstraintCase::kNoOverlap2D:
1643  is_feasible = checker.NoOverlap2DConstraintIsFeasible(model, ct);
1644  break;
1645  case ConstraintProto::ConstraintCase::kCumulative:
1646  is_feasible = checker.CumulativeConstraintIsFeasible(model, ct);
1647  break;
1648  case ConstraintProto::ConstraintCase::kElement:
1649  is_feasible = checker.ElementConstraintIsFeasible(ct);
1650  break;
1651  case ConstraintProto::ConstraintCase::kTable:
1652  is_feasible = checker.TableConstraintIsFeasible(ct);
1653  break;
1654  case ConstraintProto::ConstraintCase::kAutomaton:
1655  is_feasible = checker.AutomatonConstraintIsFeasible(ct);
1656  break;
1657  case ConstraintProto::ConstraintCase::kCircuit:
1658  is_feasible = checker.CircuitConstraintIsFeasible(ct);
1659  break;
1660  case ConstraintProto::ConstraintCase::kRoutes:
1661  is_feasible = checker.RoutesConstraintIsFeasible(ct);
1662  break;
1663  case ConstraintProto::ConstraintCase::kInverse:
1664  is_feasible = checker.InverseConstraintIsFeasible(ct);
1665  break;
1666  case ConstraintProto::ConstraintCase::kReservoir:
1667  is_feasible = checker.ReservoirConstraintIsFeasible(ct);
1668  break;
1669  case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1670  // Empty constraint is always feasible.
1671  break;
1672  default:
1673  LOG(FATAL) << "Unuspported constraint: " << ConstraintCaseName(type);
1674  }
1675 
1676  // Display a message to help debugging.
1677  if (!is_feasible) {
1678  VLOG(1) << "Failing constraint #" << c << " : "
1679  << ProtobufShortDebugString(model.constraints(c));
1680  if (mapping_proto != nullptr && postsolve_mapping != nullptr) {
1681  std::vector<int> reverse_map(mapping_proto->variables().size(), -1);
1682  for (int var = 0; var < postsolve_mapping->size(); ++var) {
1683  reverse_map[(*postsolve_mapping)[var]] = var;
1684  }
1685  for (const int var : UsedVariables(model.constraints(c))) {
1686  VLOG(1) << "var: " << var << " mapped_to: " << reverse_map[var]
1687  << " value: " << variable_values[var] << " initial_domain: "
1688  << ReadDomainFromProto(model.variables(var))
1689  << " postsolved_domain: "
1690  << ReadDomainFromProto(mapping_proto->variables(var));
1691  }
1692  } else {
1693  for (const int var : UsedVariables(model.constraints(c))) {
1694  VLOG(1) << "var: " << var << " value: " << variable_values[var];
1695  }
1696  }
1697  return false;
1698  }
1699  }
1700 
1701  // Check that the objective is within its domain.
1702  //
1703  // TODO(user): This is not really a "feasibility" question, but we should
1704  // probably check that the response objective matches with the one we can
1705  // compute here. This might better be done in another function though.
1706  if (model.has_objective()) {
1707  int64_t inner_objective = 0;
1708  const int num_variables = model.objective().coeffs_size();
1709  for (int i = 0; i < num_variables; ++i) {
1710  inner_objective += checker.Value(model.objective().vars(i)) *
1711  model.objective().coeffs(i);
1712  }
1713  if (!model.objective().domain().empty()) {
1714  if (!DomainInProtoContains(model.objective(), inner_objective)) {
1715  VLOG(1) << "Objective value " << inner_objective << " not in domain! "
1716  << ReadDomainFromProto(model.objective());
1717  return false;
1718  }
1719  }
1720  double factor = model.objective().scaling_factor();
1721  if (factor == 0.0) factor = 1.0;
1722  const double scaled_objective =
1723  factor *
1724  (static_cast<double>(inner_objective) + model.objective().offset());
1725  VLOG(2) << "Checker inner objective = " << inner_objective;
1726  VLOG(2) << "Checker scaled objective = " << scaled_objective;
1727  }
1728 
1729  return true;
1730 }
1731 
1732 } // namespace sat
1733 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
We call domain any subset of Int64 = [kint64min, kint64max].
int64_t Size() const
Returns the number of elements in the domain.
#define RETURN_IF_NOT_EMPTY(statement)
CpModelProto proto
int interval_index
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int index
absl::Status ValidateLinearExpression(const LinearExpressionProto &expression, const IdNameBiMap &variable_universe)
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool RefIsPositive(int ref)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
std::string ValidateInputCpModel(const SatParameters &params, const CpModelProto &model)
bool SolutionIsFeasible(const CpModelProto &model, absl::Span< const int64_t > variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition: integer.h:1795
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
Collection of objects used to extend the Constraint Solver library.
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
int64_t CapProd(int64_t x, int64_t y)
int64_t CapAbs(int64_t v)
std::string ProtobufDebugString(const P &message)
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
Returns true iff we have:
static int input(yyscan_t yyscanner)
int64_t demand
Definition: resource.cc:126
int64_t time
Definition: resource.cc:1694
int64_t delta
Definition: resource.cc:1695
IntervalVar * interval
Definition: resource.cc:101
int64_t capacity
int64_t tail
int64_t head
int nodes
std::optional< int64_t > end
int64_t start
#define VLOG(verboselevel)
Definition: vlog.h:39