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