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