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