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