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