Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_checker.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <limits>
20#include <optional>
21#include <string>
22#include <tuple>
23#include <utility>
24#include <vector>
25
26#include "absl/algorithm/container.h"
27#include "absl/container/btree_map.h"
28#include "absl/container/flat_hash_map.h"
29#include "absl/container/flat_hash_set.h"
30#include "absl/flags/flag.h"
31#include "absl/log/check.h"
32#include "absl/strings/str_cat.h"
33#include "absl/types/span.h"
43
44ABSL_FLAG(bool, cp_model_check_dependent_variables, false,
45 "When true, check that solutions can be computed only from their "
46 "free variables.");
47
48namespace operations_research {
49namespace sat {
50namespace {
51
52// =============================================================================
53// CpModelProto validation.
54// =============================================================================
55
56// If the string returned by "statement" is not empty, returns it.
57#define RETURN_IF_NOT_EMPTY(statement) \
58 do { \
59 const std::string error_message = statement; \
60 if (!error_message.empty()) return error_message; \
61 } while (false)
62
63template <typename ProtoWithDomain>
64bool DomainInProtoIsValid(const ProtoWithDomain& proto) {
65 if (proto.domain().size() % 2) return false;
66 std::vector<ClosedInterval> domain;
67 for (int i = 0; i < proto.domain_size(); i += 2) {
68 if (proto.domain(i) > proto.domain(i + 1)) return false;
69 domain.push_back({proto.domain(i), proto.domain(i + 1)});
70 }
72}
73
74bool VariableReferenceIsValid(const CpModelProto& model, int reference) {
75 // We do it this way to avoid overflow if reference is kint64min for instance.
76 if (reference >= model.variables_size()) return false;
77 return reference >= -static_cast<int>(model.variables_size());
78}
79
80// Note(user): Historically we always accepted positive or negative variable
81// reference everywhere, but now that we can always substitute affine relation,
82// we starts to transition to positive reference only, which are clearer. Note
83// that this doesn't concern literal reference though.
84bool VariableIndexIsValid(const CpModelProto& model, int var) {
85 return var >= 0 && var < model.variables_size();
86}
87
88bool LiteralReferenceIsValid(const CpModelProto& model, int reference) {
89 if (!VariableReferenceIsValid(model, reference)) return false;
90 const auto& var_proto = model.variables(PositiveRef(reference));
91 const int64_t min_domain = var_proto.domain(0);
92 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
93 return min_domain >= 0 && max_domain <= 1;
94}
95
96std::string ValidateIntegerVariable(const CpModelProto& model, int v) {
97 const IntegerVariableProto& proto = model.variables(v);
98 if (proto.domain_size() == 0) {
99 return absl::StrCat("var #", v,
100 " has no domain(): ", ProtobufShortDebugString(proto));
101 }
102 if (proto.domain_size() % 2 != 0) {
103 return absl::StrCat("var #", v, " has an odd domain() size: ",
105 }
106 if (!DomainInProtoIsValid(proto)) {
107 return absl::StrCat("var #", v, " has and invalid domain() format: ",
109 }
110
111 // Internally, we often take the negation of a domain, and we also want to
112 // have sentinel values greater than the min/max of a variable domain, so
113 // the domain must fall in [-kint64max / 2, kint64max / 2].
114 const int64_t lb = proto.domain(0);
115 const int64_t ub = proto.domain(proto.domain_size() - 1);
116 if (lb < -std::numeric_limits<int64_t>::max() / 2 ||
117 ub > std::numeric_limits<int64_t>::max() / 2) {
118 return absl::StrCat(
119 "var #", v, " domain do not fall in [-kint64max / 2, kint64max / 2]. ",
121 }
122
123 // We do compute ub - lb in some place in the code and do not want to deal
124 // with overflow everywhere. This seems like a reasonable precondition anyway.
125 if (lb < 0 && lb + std::numeric_limits<int64_t>::max() < ub) {
126 return absl::StrCat(
127 "var #", v,
128 " has a domain that is too large, i.e. |UB - LB| overflow an int64_t: ",
130 }
131
132 return "";
133}
134
135std::string ValidateVariablesUsedInConstraint(const CpModelProto& model,
136 int c) {
137 const ConstraintProto& ct = model.constraints(c);
139 for (const int v : references.variables) {
140 if (!VariableReferenceIsValid(model, v)) {
141 return absl::StrCat("Out of bound integer variable ", v,
142 " in constraint #", c, " : ",
144 }
145 }
146 for (const int lit : ct.enforcement_literal()) {
147 if (!LiteralReferenceIsValid(model, lit)) {
148 return absl::StrCat("Invalid enforcement literal ", lit,
149 " in constraint #", c, " : ",
151 }
152 }
153 for (const int lit : references.literals) {
154 if (!LiteralReferenceIsValid(model, lit)) {
155 return absl::StrCat("Invalid literal ", lit, " in constraint #", c, " : ",
157 }
158 }
159 return "";
160}
161
162std::string ValidateIntervalsUsedInConstraint(bool after_presolve,
163 const CpModelProto& model,
164 int c) {
165 const ConstraintProto& ct = model.constraints(c);
166 for (const int i : UsedIntervals(ct)) {
167 if (i < 0 || i >= model.constraints_size()) {
168 return absl::StrCat("Out of bound interval ", i, " in constraint #", c,
169 " : ", ProtobufShortDebugString(ct));
170 }
171 if (after_presolve && i >= c) {
172 return absl::StrCat("Interval ", i, " in constraint #", c,
173 " must appear before in the list of constraints :",
175 }
176 if (model.constraints(i).constraint_case() !=
178 return absl::StrCat(
179 "Interval ", i,
180 " does not refer to an interval constraint. Problematic constraint #",
181 c, " : ", ProtobufShortDebugString(ct));
182 }
183 }
184 return "";
185}
186
187int64_t MinOfRef(const CpModelProto& model, int ref) {
188 const IntegerVariableProto& var_proto = model.variables(PositiveRef(ref));
189 if (RefIsPositive(ref)) {
190 return var_proto.domain(0);
191 } else {
192 return -var_proto.domain(var_proto.domain_size() - 1);
193 }
194}
195
196int64_t MaxOfRef(const CpModelProto& model, int ref) {
197 const IntegerVariableProto& var_proto = model.variables(PositiveRef(ref));
198 if (RefIsPositive(ref)) {
199 return var_proto.domain(var_proto.domain_size() - 1);
200 } else {
201 return -var_proto.domain(0);
202 }
203}
204
205template <class LinearExpressionProto>
206int64_t MinOfExpression(const CpModelProto& model,
207 const LinearExpressionProto& proto) {
208 int64_t sum_min = proto.offset();
209 for (int i = 0; i < proto.vars_size(); ++i) {
210 const int ref = proto.vars(i);
211 const int64_t coeff = proto.coeffs(i);
212 sum_min =
213 CapAdd(sum_min, coeff >= 0 ? CapProd(MinOfRef(model, ref), coeff)
214 : CapProd(MaxOfRef(model, ref), coeff));
215 }
216
217 return sum_min;
218}
219
220template <class LinearExpressionProto>
221int64_t MaxOfExpression(const CpModelProto& model,
222 const LinearExpressionProto& proto) {
223 int64_t sum_max = proto.offset();
224 for (int i = 0; i < proto.vars_size(); ++i) {
225 const int ref = proto.vars(i);
226 const int64_t coeff = proto.coeffs(i);
227 sum_max =
228 CapAdd(sum_max, coeff >= 0 ? CapProd(MaxOfRef(model, ref), coeff)
229 : CapProd(MinOfRef(model, ref), coeff));
230 }
231
232 return sum_max;
233}
234
235bool ExpressionIsFixed(const CpModelProto& model,
236 const LinearExpressionProto& expr) {
237 for (int i = 0; i < expr.vars_size(); ++i) {
238 if (expr.coeffs(i) == 0) continue;
239 const IntegerVariableProto& var_proto = model.variables(expr.vars(i));
240 if (var_proto.domain_size() != 2 ||
241 var_proto.domain(0) != var_proto.domain(1)) {
242 return false;
243 }
244 }
245 return true;
246}
247
248int64_t ExpressionFixedValue(const CpModelProto& model,
249 const LinearExpressionProto& expr) {
250 DCHECK(ExpressionIsFixed(model, expr));
251 return MinOfExpression(model, expr);
252}
253
254int64_t IntervalSizeMax(const CpModelProto& model, int interval_index) {
256 model.constraints(interval_index).constraint_case());
257 const IntervalConstraintProto& proto =
258 model.constraints(interval_index).interval();
259 return MaxOfExpression(model, proto.size());
260}
261
262Domain DomainOfRef(const CpModelProto& model, int ref) {
263 const Domain domain = ReadDomainFromProto(model.variables(PositiveRef(ref)));
264 return RefIsPositive(ref) ? domain : domain.Negation();
265}
266
267std::string ValidateLinearExpression(const CpModelProto& model,
268 const LinearExpressionProto& expr) {
269 if (expr.coeffs_size() != expr.vars_size()) {
270 return absl::StrCat("coeffs_size() != vars_size() in linear expression: ",
272 }
273 if (PossibleIntegerOverflow(model, expr.vars(), expr.coeffs(),
274 expr.offset())) {
275 return absl::StrCat("Possible overflow in linear expression: ",
277 }
278 for (const int var : expr.vars()) {
279 if (!RefIsPositive(var)) {
280 return absl::StrCat("Invalid negated variable in linear expression: ",
282 }
283 }
284 return "";
285}
286
287std::string ValidateLinearConstraint(const CpModelProto& model,
288 const ConstraintProto& ct) {
289 if (!DomainInProtoIsValid(ct.linear())) {
290 return absl::StrCat("Invalid domain in constraint : ",
292 }
293 if (ct.linear().coeffs_size() != ct.linear().vars_size()) {
294 return absl::StrCat("coeffs_size() != vars_size() in constraint: ",
296 }
297 for (const int var : ct.linear().vars()) {
298 if (!RefIsPositive(var)) {
299 return absl::StrCat("Invalid negated variable in linear constraint: ",
301 }
302 }
303 const LinearConstraintProto& arg = ct.linear();
304 if (PossibleIntegerOverflow(model, arg.vars(), arg.coeffs())) {
305 return "Possible integer overflow in constraint: " +
307 }
308 return "";
309}
310
311std::string ValidateIntModConstraint(const CpModelProto& model,
312 const ConstraintProto& ct) {
313 if (ct.int_mod().exprs().size() != 2) {
314 return absl::StrCat("An int_mod constraint should have exactly 2 terms: ",
316 }
317 if (!ct.int_mod().has_target()) {
318 return absl::StrCat("An int_mod constraint should have a target: ",
320 }
321
322 RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, ct.int_mod().exprs(0)));
323 RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, ct.int_mod().exprs(1)));
324 RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, ct.int_mod().target()));
325
326 const LinearExpressionProto mod_expr = ct.int_mod().exprs(1);
327 if (MinOfExpression(model, mod_expr) <= 0) {
328 return absl::StrCat(
329 "An int_mod must have a strictly positive modulo argument: ",
331 }
332
333 return "";
334}
335
336std::string ValidateIntProdConstraint(const CpModelProto& model,
337 const ConstraintProto& ct) {
338 if (!ct.int_prod().has_target()) {
339 return absl::StrCat("An int_prod constraint should have a target: ",
341 }
342
343 for (const LinearExpressionProto& expr : ct.int_prod().exprs()) {
345 }
346 RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, ct.int_prod().target()));
347
348 // Detect potential overflow.
349 Domain product_domain(1);
350 for (const LinearExpressionProto& expr : ct.int_prod().exprs()) {
351 const int64_t min_expr = MinOfExpression(model, expr);
352 const int64_t max_expr = MaxOfExpression(model, expr);
353 if (min_expr == 0 && max_expr == 0) {
354 // An overflow multiplied by zero is still invalid.
355 continue;
356 }
357 product_domain =
358 product_domain.ContinuousMultiplicationBy({min_expr, max_expr});
359 }
360
361 if (product_domain.Max() <= -std ::numeric_limits<int64_t>::max() ||
362 product_domain.Min() >= std::numeric_limits<int64_t>::max()) {
363 return absl::StrCat("integer overflow in constraint: ",
365 }
366
367 // We need to expand the product when its arity is > 2. In that case, we must
368 // be strict with overflows.
369 if (ct.int_prod().exprs_size() > 2 &&
370 (product_domain.Max() >= std ::numeric_limits<int64_t>::max() ||
371 product_domain.Min() <= -std::numeric_limits<int64_t>::max())) {
372 return absl::StrCat("Potential integer overflow in constraint: ",
374 }
375
376 return "";
377}
378
379std::string ValidateIntDivConstraint(const CpModelProto& model,
380 const ConstraintProto& ct) {
381 if (ct.int_div().exprs().size() != 2) {
382 return absl::StrCat("An int_div constraint should have exactly 2 terms: ",
384 }
385 if (!ct.int_div().has_target()) {
386 return absl::StrCat("An int_div constraint should have a target: ",
388 }
389
390 RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, ct.int_div().exprs(0)));
391 RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, ct.int_div().exprs(1)));
392 RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, ct.int_div().target()));
393
394 const LinearExpressionProto& denom = ct.int_div().exprs(1);
395 const int64_t offset = denom.offset();
396 if (ExpressionIsFixed(model, denom)) {
397 if (ExpressionFixedValue(model, denom) == 0) {
398 return absl::StrCat("Division by 0: ", ProtobufShortDebugString(ct));
399 }
400 } else {
401 const int64_t coeff = denom.coeffs(0);
402 CHECK_NE(coeff, 0);
403 const int64_t inverse_of_zero = -offset / coeff;
404 if (inverse_of_zero * coeff + offset == 0 &&
405 DomainOfRef(model, denom.vars(0)).Contains(inverse_of_zero)) {
406 return absl::StrCat("The domain of the divisor cannot contain 0: ",
408 }
409 }
410 return "";
411}
412
413void AppendToOverflowValidator(const LinearExpressionProto& input,
414 LinearExpressionProto* output,
415 int64_t prod = 1) {
416 output->mutable_vars()->Add(input.vars().begin(), input.vars().end());
417 for (const int64_t coeff : input.coeffs()) {
418 output->add_coeffs(coeff * prod);
419 }
420
421 // We add the absolute value to be sure that future computation will not
422 // overflow depending on the order they are performed in.
423 output->set_offset(
424 CapAdd(std::abs(output->offset()), std::abs(input.offset())));
425}
426
427std::string ValidateElementConstraint(const CpModelProto& model,
428 const ConstraintProto& ct) {
429 const ElementConstraintProto& element = ct.element();
430
431 const bool in_linear_format = element.has_linear_index() ||
432 element.has_linear_target() ||
433 !element.exprs().empty();
434 const bool in_legacy_format =
435 !element.vars().empty() || element.index() != 0 || element.target() != 0;
436 if (in_linear_format && in_legacy_format) {
437 return absl::StrCat(
438 "Inconsistent element with both legacy and new format defined",
440 }
441
442 if (element.vars().empty() && element.exprs().empty()) {
443 return "Empty element constraint is interpreted as vars[], thus invalid "
444 "since the index will be out of bounds.";
445 }
446
447 // We need to be able to manipulate expression like "target - var" without
448 // integer overflow.
449 if (!element.vars().empty()) {
450 LinearExpressionProto overflow_detection;
451 overflow_detection.add_vars(element.target());
452 overflow_detection.add_coeffs(1);
453 overflow_detection.add_vars(/*dummy*/ 0);
454 overflow_detection.add_coeffs(-1);
455 for (const int ref : element.vars()) {
456 if (!VariableIndexIsValid(model, ref)) {
457 return absl::StrCat("Element vars must be valid variables: ",
459 }
460 overflow_detection.set_vars(1, ref);
461 if (PossibleIntegerOverflow(model, overflow_detection.vars(),
462 overflow_detection.coeffs())) {
463 return absl::StrCat(
464 "Domain of the variables involved in element constraint may cause "
465 "overflow",
467 }
468 }
469 }
470
471 if (in_legacy_format) {
472 if (!VariableIndexIsValid(model, element.index()) ||
473 !VariableIndexIsValid(model, element.target())) {
474 return absl::StrCat(
475 "Element constraint index and target must valid variables: ",
477 }
478 }
479
480 if (in_linear_format) {
482 ValidateLinearExpression(model, element.linear_index()));
484 ValidateLinearExpression(model, element.linear_target()));
485 for (const LinearExpressionProto& expr : element.exprs()) {
487 LinearExpressionProto overflow_detection = ct.element().linear_target();
488 AppendToOverflowValidator(expr, &overflow_detection, -1);
489 const int64_t offset = CapSub(overflow_detection.offset(), expr.offset());
490 overflow_detection.set_offset(offset);
491 if (PossibleIntegerOverflow(model, overflow_detection.vars(),
492 overflow_detection.coeffs(),
493 overflow_detection.offset())) {
494 return absl::StrCat(
495 "Domain of the variables involved in element constraint may cause "
496 "overflow");
497 }
498 }
499 }
500
501 return "";
502}
503
504std::string ValidateInverseConstraint(const CpModelProto& model,
505 const ConstraintProto& ct) {
506 if (ct.inverse().f_direct().size() != ct.inverse().f_inverse().size()) {
507 return absl::StrCat("Non-matching fields size in inverse: ",
509 }
510 const InverseConstraintProto& inverse = ct.inverse();
511 for (const auto* vars : {&inverse.f_direct(), &inverse.f_inverse()}) {
512 for (const int var : *vars) {
513 if (!VariableIndexIsValid(model, var)) {
514 return absl::StrCat("Invalid variable index in inverse constraint: ",
515 var);
516 }
517 }
518 }
519 return "";
520}
521
522std::string ValidateTableConstraint(const CpModelProto& model,
523 const ConstraintProto& ct) {
524 const TableConstraintProto& arg = ct.table();
525 if (!arg.vars().empty() && !arg.exprs().empty()) {
526 return absl::StrCat(
527 "Inconsistent table with both legacy and new format defined: ",
529 }
530 if (arg.vars().empty() && arg.exprs().empty() && !arg.values().empty()) {
531 return absl::StrCat(
532 "Inconsistent table empty expressions and non-empty tuples: ",
534 }
535 if (arg.vars().empty() && arg.exprs().empty() && arg.values().empty()) {
536 return "";
537 }
538 const int arity = arg.vars().empty() ? arg.exprs().size() : arg.vars().size();
539 if (arg.values().size() % arity != 0) {
540 return absl::StrCat(
541 "The flat encoding of a table constraint tuples must be a multiple of "
542 "the number of expressions: ",
544 }
545 for (const int var : arg.vars()) {
546 if (!VariableIndexIsValid(model, var)) {
547 return absl::StrCat("Invalid variable index in table constraint: ", var);
548 }
549 }
550 for (const LinearExpressionProto& expr : arg.exprs()) {
552 }
553 return "";
554}
555
556std::string ValidateAutomatonConstraint(const CpModelProto& model,
557 const ConstraintProto& ct) {
558 const AutomatonConstraintProto& automaton = ct.automaton();
559 if (!automaton.vars().empty() && !automaton.exprs().empty()) {
560 return absl::StrCat(
561 "Inconsistent automaton with both legacy and new format defined: ",
563 }
564 const int num_transistions = automaton.transition_tail().size();
565 if (num_transistions != automaton.transition_head().size() ||
566 num_transistions != automaton.transition_label().size()) {
567 return absl::StrCat(
568 "The transitions repeated fields must have the same size: ",
570 }
571 for (const int var : automaton.vars()) {
572 if (!VariableIndexIsValid(model, var)) {
573 return absl::StrCat("Invalid variable index in automaton constraint: ",
574 var);
575 }
576 }
577 for (const LinearExpressionProto& expr : automaton.exprs()) {
579 }
580 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> tail_label_to_head;
581 for (int i = 0; i < num_transistions; ++i) {
582 const int64_t tail = automaton.transition_tail(i);
583 const int64_t head = automaton.transition_head(i);
584 const int64_t label = automaton.transition_label(i);
585 if (label <= std::numeric_limits<int64_t>::min() + 1 ||
586 label == std::numeric_limits<int64_t>::max()) {
587 return absl::StrCat("labels in the automaton constraint are too big: ",
588 label);
589 }
590 const auto [it, inserted] =
591 tail_label_to_head.insert({{tail, label}, head});
592 if (!inserted) {
593 if (it->second == head) {
594 return absl::StrCat("automaton: duplicate transition ", tail, " --(",
595 label, ")--> ", head);
596 } else {
597 return absl::StrCat("automaton: incompatible transitions ", tail,
598 " --(", label, ")--> ", head, " and ", tail, " --(",
599 label, ")--> ", it->second);
600 }
601 }
602 }
603 return "";
604}
605
606template <typename GraphProto>
607std::string ValidateGraphInput(bool is_route, const GraphProto& graph) {
608 const int size = graph.tails().size();
609 if (graph.heads().size() != size || graph.literals().size() != size) {
610 return absl::StrCat("Wrong field sizes in graph: ",
612 }
613
614 // We currently disallow multiple self-loop on the same node.
615 absl::flat_hash_set<int> self_loops;
616 for (int i = 0; i < size; ++i) {
617 if (graph.heads(i) != graph.tails(i)) continue;
618 if (!self_loops.insert(graph.heads(i)).second) {
619 return absl::StrCat(
620 "Circuit/Route constraint contains multiple self-loop involving "
621 "node ",
622 graph.heads(i));
623 }
624 if (is_route && graph.tails(i) == 0) {
625 return absl::StrCat(
626 "A route constraint cannot have a self-loop on the depot (node 0)");
627 }
628 }
629
630 return "";
631}
632
633std::string ValidateRoutesConstraint(const CpModelProto& model,
634 const ConstraintProto& ct) {
635 int max_node = 0;
636 absl::flat_hash_set<int> nodes;
637 for (const int node : ct.routes().tails()) {
638 if (node < 0) {
639 return "All node in a route constraint must be in [0, num_nodes)";
640 }
641 nodes.insert(node);
642 max_node = std::max(max_node, node);
643 }
644 for (const int node : ct.routes().heads()) {
645 if (node < 0) {
646 return "All node in a route constraint must be in [0, num_nodes)";
647 }
648 nodes.insert(node);
649 max_node = std::max(max_node, node);
650 }
651 if (!nodes.empty() && max_node != nodes.size() - 1) {
652 return absl::StrCat(
653 "All nodes in a route constraint must have incident arcs");
654 }
655
656 for (const RoutesConstraintProto::NodeExpressions& dimension :
657 ct.routes().dimensions()) {
658 if (dimension.exprs().size() != nodes.size()) {
659 return absl::StrCat(
660 "If the dimensions field in a route constraint is set, its elements "
661 "must be of size num_nodes:",
662 nodes.size());
663 }
664 for (const LinearExpressionProto& expr : dimension.exprs()) {
665 for (const int v : expr.vars()) {
666 if (!VariableReferenceIsValid(model, v)) {
667 return absl::StrCat("Out of bound integer variable ", v,
668 " in route constraint ",
670 }
671 }
673 }
674 }
675
676 return ValidateGraphInput(/*is_route=*/true, ct.routes());
677}
678
679std::string ValidateIntervalConstraint(const CpModelProto& model,
680 const ConstraintProto& ct) {
681 const IntervalConstraintProto& arg = ct.interval();
682
683 if (!arg.has_start()) {
684 return absl::StrCat("Interval must have a start expression: ",
686 }
687 if (!arg.has_size()) {
688 return absl::StrCat("Interval must have a size expression: ",
690 }
691 if (!arg.has_end()) {
692 return absl::StrCat("Interval must have a end expression: ",
694 }
695
696 LinearExpressionProto for_overflow_validation;
697 if (arg.start().vars_size() > 1) {
698 return "Interval with a start expression containing more than one "
699 "variable are currently not supported.";
700 }
702 AppendToOverflowValidator(arg.start(), &for_overflow_validation);
703 if (arg.size().vars_size() > 1) {
704 return "Interval with a size expression containing more than one "
705 "variable are currently not supported.";
706 }
708 if (ct.enforcement_literal().empty() &&
709 MinOfExpression(model, arg.size()) < 0) {
710 return absl::StrCat(
711 "The size of a performed interval must be >= 0 in constraint: ",
713 }
714 AppendToOverflowValidator(arg.size(), &for_overflow_validation);
715 if (arg.end().vars_size() > 1) {
716 return "Interval with a end expression containing more than one "
717 "variable are currently not supported.";
718 }
720 AppendToOverflowValidator(arg.end(), &for_overflow_validation, -1);
721
722 if (PossibleIntegerOverflow(model, for_overflow_validation.vars(),
723 for_overflow_validation.coeffs(),
724 for_overflow_validation.offset())) {
725 return absl::StrCat("Possible overflow in interval: ",
726 ProtobufShortDebugString(ct.interval()));
727 }
728
729 return "";
730}
731
732std::string ValidateCumulativeConstraint(const CpModelProto& model,
733 const ConstraintProto& ct) {
734 if (ct.cumulative().intervals_size() != ct.cumulative().demands_size()) {
735 return absl::StrCat("intervals_size() != demands_size() in constraint: ",
737 }
738
740 ValidateLinearExpression(model, ct.cumulative().capacity()));
741 for (const LinearExpressionProto& demand : ct.cumulative().demands()) {
743 }
744
745 for (const LinearExpressionProto& demand_expr : ct.cumulative().demands()) {
746 if (MinOfExpression(model, demand_expr) < 0) {
747 return absl::StrCat(
748 "Demand ", ProtobufDebugString(demand_expr),
749 " must be positive in constraint: ", ProtobufDebugString(ct));
750 }
751 if (demand_expr.vars_size() > 1) {
752 return absl::StrCat("Demand ", ProtobufDebugString(demand_expr),
753 " must be affine or constant in constraint: ",
755 }
756 }
757 if (ct.cumulative().capacity().vars_size() > 1) {
758 return absl::StrCat(
759 "capacity ", ProtobufDebugString(ct.cumulative().capacity()),
760 " must be affine or constant in constraint: ", ProtobufDebugString(ct));
761 }
762
763 int64_t sum_max_demands = 0;
764 for (const LinearExpressionProto& demand_expr : ct.cumulative().demands()) {
765 const int64_t demand_max = MaxOfExpression(model, demand_expr);
766 DCHECK_GE(demand_max, 0);
767 sum_max_demands = CapAdd(sum_max_demands, demand_max);
768 if (sum_max_demands == std::numeric_limits<int64_t>::max()) {
769 return "The sum of max demands do not fit on an int64_t in constraint: " +
771 }
772 }
773
774 return "";
775}
776
777std::string ValidateNoOverlap2DConstraint(const CpModelProto& model,
778 const ConstraintProto& ct) {
779 const int size_x = ct.no_overlap_2d().x_intervals().size();
780 const int size_y = ct.no_overlap_2d().y_intervals().size();
781 if (size_x != size_y) {
782 return absl::StrCat("The two lists of intervals must have the same size: ",
784 }
785
786 // Checks if the sum of max areas of each rectangle can overflow.
787 int64_t sum_max_areas = 0;
788 for (int i = 0; i < ct.no_overlap_2d().x_intervals().size(); ++i) {
789 const int64_t max_size_x =
790 IntervalSizeMax(model, ct.no_overlap_2d().x_intervals(i));
791 const int64_t max_size_y =
792 IntervalSizeMax(model, ct.no_overlap_2d().y_intervals(i));
793 sum_max_areas = CapAdd(sum_max_areas, CapProd(max_size_x, max_size_y));
794 if (sum_max_areas == std::numeric_limits<int64_t>::max()) {
795 return "Integer overflow when summing all areas in "
796 "constraint: " +
798 }
799 }
800 return "";
801}
802
803std::string ValidateReservoirConstraint(const CpModelProto& model,
804 const ConstraintProto& ct) {
805 if (ct.reservoir().time_exprs().size() !=
806 ct.reservoir().level_changes().size()) {
807 return absl::StrCat(
808 "time_exprs and level_changes fields must be of the same size: ",
810 }
811 for (const LinearExpressionProto& expr : ct.reservoir().time_exprs()) {
813 // We want to be able to safely put time_exprs[i]-time_exprs[j] in a linear.
814 if (MinOfExpression(model, expr) <=
815 -std::numeric_limits<int64_t>::max() / 4 ||
816 MaxOfExpression(model, expr) >=
817 std::numeric_limits<int64_t>::max() / 4) {
818 return absl::StrCat(
819 "Potential integer overflow on time_expr of a reservoir: ",
821 }
822 }
823 for (const LinearExpressionProto& expr : ct.reservoir().level_changes()) {
825 }
826 if (ct.reservoir().min_level() > 0) {
827 return absl::StrCat(
828 "The min level of a reservoir must be <= 0. Please use fixed events to "
829 "setup initial state: ",
831 }
832 if (ct.reservoir().max_level() < 0) {
833 return absl::StrCat(
834 "The max level of a reservoir must be >= 0. Please use fixed events to "
835 "setup initial state: ",
837 }
838
839 int64_t sum_abs = 0;
840 for (const LinearExpressionProto& demand : ct.reservoir().level_changes()) {
841 // We test for min int64_t before the abs().
842 const int64_t demand_min = MinOfExpression(model, demand);
843 const int64_t demand_max = MaxOfExpression(model, demand);
844 sum_abs = CapAdd(sum_abs, std::max(CapAbs(demand_min), CapAbs(demand_max)));
845 if (sum_abs == std::numeric_limits<int64_t>::max()) {
846 return "Possible integer overflow in constraint: " +
848 }
849 }
850 if (ct.reservoir().active_literals_size() > 0 &&
851 ct.reservoir().active_literals_size() !=
852 ct.reservoir().time_exprs_size()) {
853 return "Wrong array length of active_literals variables";
854 }
855 if (ct.reservoir().level_changes_size() > 0 &&
856 ct.reservoir().level_changes_size() != ct.reservoir().time_exprs_size()) {
857 return "Wrong array length of level_changes variables";
858 }
859 return "";
860}
861
862std::string ValidateObjective(const CpModelProto& model,
863 const CpObjectiveProto& obj) {
864 if (!DomainInProtoIsValid(obj)) {
865 return absl::StrCat("The objective has and invalid domain() format: ",
867 }
868 if (obj.vars().size() != obj.coeffs().size()) {
869 return absl::StrCat("vars and coeffs size do not match in objective: ",
871 }
872 for (const int v : obj.vars()) {
873 if (!VariableIndexIsValid(model, v)) {
874 return absl::StrCat("Out of bound integer variable ", v,
875 " in objective: ", ProtobufShortDebugString(obj));
876 }
877 }
878 std::pair<int64_t, int64_t> bounds;
879 if (PossibleIntegerOverflow(model, obj.vars(), obj.coeffs(), 0, &bounds)) {
880 return "Possible integer overflow in objective: " +
882 }
883 if (!std::isfinite(model.objective().offset())) {
884 return "Objective offset must be finite: " + ProtobufDebugString(obj);
885 }
886 if (model.objective().scaling_factor() != 0 &&
887 model.objective().scaling_factor() != 1 &&
888 model.objective().scaling_factor() != -1) {
889 if (!std::isfinite(
890 std::abs(model.objective().scaling_factor() * bounds.first) +
891 std::abs(model.objective().offset())) ||
892 !std::isfinite(
893 std::abs(model.objective().scaling_factor() * bounds.second) +
894 std::abs(model.objective().offset()))) {
895 return "Possible floating point overflow in objective when multiplied by "
896 "the scaling factor: " +
898 }
899 }
900 return "";
901}
902
903std::string ValidateFloatingPointObjective(double max_valid_magnitude,
904 const CpModelProto& model,
905 const FloatObjectiveProto& obj) {
906 if (obj.vars().size() != obj.coeffs().size()) {
907 return absl::StrCat("vars and coeffs size do not match in objective: ",
909 }
910 for (const int v : obj.vars()) {
911 if (!VariableIndexIsValid(model, v)) {
912 return absl::StrCat("Out of bound integer variable ", v,
913 " in objective: ", ProtobufShortDebugString(obj));
914 }
915 }
916 for (const double coeff : obj.coeffs()) {
917 if (!std::isfinite(coeff)) {
918 return absl::StrCat("Coefficients must be finite in objective: ",
920 }
921 if (std::abs(coeff) > max_valid_magnitude) {
922 return absl::StrCat(
923 "Coefficients larger than params.mip_max_valid_magnitude() [value = ",
924 max_valid_magnitude,
925 "] in objective: ", ProtobufShortDebugString(obj));
926 }
927 }
928 if (!std::isfinite(obj.offset())) {
929 return absl::StrCat("Offset must be finite in objective: ",
931 }
932 double sum_min = obj.offset();
933 double sum_max = obj.offset();
934 for (int i = 0; i < obj.vars().size(); ++i) {
935 const int ref = obj.vars(i);
936 const auto& var_proto = model.variables(PositiveRef(ref));
937 const int64_t min_domain = var_proto.domain(0);
938 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
939 const double coeff = RefIsPositive(ref) ? obj.coeffs(i) : -obj.coeffs(i);
940 const double prod1 = min_domain * coeff;
941 const double prod2 = max_domain * coeff;
942
943 // Note that we use min/max with zero to disallow "alternative" terms and
944 // be sure that we cannot have an overflow if we do the computation in a
945 // different order.
946 sum_min += std::min(0.0, std::min(prod1, prod2));
947 sum_max += std::max(0.0, std::max(prod1, prod2));
948 }
949 if (!std::isfinite(2.0 * sum_min) || !std::isfinite(2.0 * sum_max)) {
950 return absl::StrCat("Possible floating point overflow in objective: ",
952 }
953 return "";
954}
955
956std::string ValidateSearchStrategies(const CpModelProto& model) {
957 for (const DecisionStrategyProto& strategy : model.search_strategy()) {
958 const int vss = strategy.variable_selection_strategy();
964 return absl::StrCat(
965 "Unknown or unsupported variable_selection_strategy: ", vss);
966 }
967 const int drs = strategy.domain_reduction_strategy();
974 return absl::StrCat("Unknown or unsupported domain_reduction_strategy: ",
975 drs);
976 }
977 if (!strategy.variables().empty() && !strategy.exprs().empty()) {
978 return absl::StrCat("Strategy can't have both variables and exprs: ",
979 ProtobufShortDebugString(strategy));
980 }
981 for (const int ref : strategy.variables()) {
982 if (!VariableReferenceIsValid(model, ref)) {
983 return absl::StrCat("Invalid variable reference in strategy: ",
984 ProtobufShortDebugString(strategy));
985 }
987 ReadDomainFromProto(model.variables(PositiveRef(ref))).Size() >
988 100000) {
989 return absl::StrCat("Variable #", PositiveRef(ref),
990 " has a domain too large to be used in a"
991 " SELECT_MEDIAN_VALUE value selection strategy");
992 }
993 }
994 for (const LinearExpressionProto& expr : strategy.exprs()) {
995 if (expr.vars_size() > 1) {
996 return absl::StrCat("expression must be affine in strategy: ",
997 ProtobufShortDebugString(strategy));
998 }
999 for (const int var : expr.vars()) {
1000 if (!VariableReferenceIsValid(model, var)) {
1001 return absl::StrCat("Invalid variable reference in strategy: ",
1002 ProtobufShortDebugString(strategy));
1003 }
1004 }
1005 if (!ValidateLinearExpression(model, expr).empty()) {
1006 return absl::StrCat("Invalid affine expr in strategy: ",
1007 ProtobufShortDebugString(strategy));
1008 }
1010 for (const int var : expr.vars()) {
1011 if (ReadDomainFromProto(model.variables(var)).Size() > 100000) {
1012 return absl::StrCat(
1013 "Variable #", var,
1014 " has a domain too large to be used in a"
1015 " SELECT_MEDIAN_VALUE value selection strategy");
1016 }
1017 }
1018 }
1019 }
1020 }
1021 return "";
1022}
1023
1024std::string ValidateSolutionHint(const CpModelProto& model) {
1025 if (!model.has_solution_hint()) return "";
1026 const auto& hint = model.solution_hint();
1027 if (hint.vars().size() != hint.values().size()) {
1028 return "Invalid solution hint: vars and values do not have the same size.";
1029 }
1030 for (const int var : hint.vars()) {
1031 if (!VariableIndexIsValid(model, var)) {
1032 return absl::StrCat("Invalid variable in solution hint: ", var);
1033 }
1034 }
1035
1036 // Reject hints with duplicate variables as this is likely a user error.
1037 absl::flat_hash_set<int> indices;
1038 for (const int var : hint.vars()) {
1039 const auto insert = indices.insert(PositiveRef(var));
1040 if (!insert.second) {
1041 return absl::StrCat(
1042 "The solution hint contains duplicate variables like the variable "
1043 "with index #",
1044 PositiveRef(var));
1045 }
1046 }
1047
1048 // Reject hints equals to INT_MIN or INT_MAX.
1049 for (const int64_t value : hint.values()) {
1050 if (value == std::numeric_limits<int64_t>::min() ||
1051 value == std::numeric_limits<int64_t>::max()) {
1052 return "The solution hint cannot contains the INT_MIN or INT_MAX values.";
1053 }
1054 }
1055
1056 return "";
1057}
1058
1059} // namespace
1060
1061bool PossibleIntegerOverflow(const CpModelProto& model,
1062 absl::Span<const int> vars,
1063 absl::Span<const int64_t> coeffs, int64_t offset,
1064 std::pair<int64_t, int64_t>* implied_domain) {
1065 if (offset == std::numeric_limits<int64_t>::min()) return true;
1066 int64_t sum_min = -std::abs(offset);
1067 int64_t sum_max = +std::abs(offset);
1068 for (int i = 0; i < vars.size(); ++i) {
1069 const int ref = vars[i];
1070 const auto& var_proto = model.variables(PositiveRef(ref));
1071 const int64_t min_domain = var_proto.domain(0);
1072 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
1073 if (coeffs[i] == std::numeric_limits<int64_t>::min()) return true;
1074 const int64_t coeff = RefIsPositive(ref) ? coeffs[i] : -coeffs[i];
1075 const int64_t prod1 = CapProd(min_domain, coeff);
1076 const int64_t prod2 = CapProd(max_domain, coeff);
1077
1078 // Note that we use min/max with zero to disallow "alternative" terms and
1079 // be sure that we cannot have an overflow if we do the computation in a
1080 // different order.
1081 sum_min = CapAdd(sum_min, std::min(int64_t{0}, std::min(prod1, prod2)));
1082 sum_max = CapAdd(sum_max, std::max(int64_t{0}, std::max(prod1, prod2)));
1083 for (const int64_t v : {prod1, prod2, sum_min, sum_max}) {
1084 if (AtMinOrMaxInt64(v)) return true;
1085 }
1086 }
1087
1088 // In addition to computing the min/max possible sum, we also often compare
1089 // it with the constraint bounds, so we do not want max - min to overflow.
1090 // We might also create an intermediate variable to represent the sum.
1091 //
1092 // Note that it is important to be symmetric here, as we do not want expr to
1093 // pass but not -expr!
1094 if (sum_min < -std::numeric_limits<int64_t>::max() / 2) return true;
1095 if (sum_max > std::numeric_limits<int64_t>::max() / 2) return true;
1096 if (implied_domain) {
1097 *implied_domain = {sum_min, sum_max};
1098 }
1099 return false;
1100}
1101
1102std::string ValidateCpModel(const CpModelProto& model, bool after_presolve) {
1103 int64_t int128_overflow = 0;
1104 for (int v = 0; v < model.variables_size(); ++v) {
1105 RETURN_IF_NOT_EMPTY(ValidateIntegerVariable(model, v));
1106
1107 const auto& domain = model.variables(v).domain();
1108 const int64_t min = domain[0];
1109 const int64_t max = domain[domain.size() - 1];
1110 int128_overflow = CapAdd(
1111 int128_overflow, std::max({std::abs(min), std::abs(max), max - min}));
1112 }
1113
1114 // We require this precondition so that we can take any linear combination of
1115 // variable with coefficient in int64_t and compute the activity on an int128
1116 // with no overflow. This is useful during cut computation.
1117 if (int128_overflow == std::numeric_limits<int64_t>::max()) {
1118 return "The sum of all variable domains do not fit on an int64_t. This is "
1119 "needed to prevent overflows.";
1120 }
1121
1122 // We need to validate the intervals used first, so we add these constraints
1123 // here so that we can validate them in a second pass.
1124 std::vector<int> constraints_using_intervals;
1125
1126 for (int c = 0; c < model.constraints_size(); ++c) {
1127 RETURN_IF_NOT_EMPTY(ValidateVariablesUsedInConstraint(model, c));
1128
1129 // Other non-generic validations.
1130 const ConstraintProto& ct = model.constraints(c);
1131 switch (ct.constraint_case()) {
1137 break;
1139 RETURN_IF_NOT_EMPTY(ValidateLinearConstraint(model, ct));
1140 break;
1143 ValidateLinearExpression(model, ct.lin_max().target()));
1144 for (const LinearExpressionProto& expr : ct.lin_max().exprs()) {
1145 RETURN_IF_NOT_EMPTY(ValidateLinearExpression(model, expr));
1146 }
1147 break;
1148 }
1150 RETURN_IF_NOT_EMPTY(ValidateIntProdConstraint(model, ct));
1151 break;
1153 RETURN_IF_NOT_EMPTY(ValidateIntDivConstraint(model, ct));
1154 break;
1156 RETURN_IF_NOT_EMPTY(ValidateIntModConstraint(model, ct));
1157 break;
1159 RETURN_IF_NOT_EMPTY(ValidateInverseConstraint(model, ct));
1160 break;
1162 for (const LinearExpressionProto& expr : ct.all_diff().exprs()) {
1164 }
1165 break;
1167 RETURN_IF_NOT_EMPTY(ValidateElementConstraint(model, ct));
1168 break;
1170 RETURN_IF_NOT_EMPTY(ValidateTableConstraint(model, ct));
1171 break;
1173 RETURN_IF_NOT_EMPTY(ValidateAutomatonConstraint(model, ct));
1174 break;
1177 ValidateGraphInput(/*is_route=*/false, ct.circuit()));
1178 break;
1180 RETURN_IF_NOT_EMPTY(ValidateRoutesConstraint(model, ct));
1181 break;
1183 RETURN_IF_NOT_EMPTY(ValidateIntervalConstraint(model, ct));
1184 break;
1186 constraints_using_intervals.push_back(c);
1187 break;
1189 constraints_using_intervals.push_back(c);
1190 break;
1192 constraints_using_intervals.push_back(c);
1193 break;
1195 RETURN_IF_NOT_EMPTY(ValidateReservoirConstraint(model, ct));
1196 break;
1198 return "The dummy constraint should never appear in a model.";
1199 default:
1200 break;
1201 }
1202 }
1203
1204 // Extra validation for constraint using intervals.
1205 for (const int c : constraints_using_intervals) {
1207 ValidateIntervalsUsedInConstraint(after_presolve, model, c));
1208
1209 const ConstraintProto& ct = model.constraints(c);
1210 switch (ct.constraint_case()) {
1212 RETURN_IF_NOT_EMPTY(ValidateCumulativeConstraint(model, ct));
1213 break;
1215 break;
1217 RETURN_IF_NOT_EMPTY(ValidateNoOverlap2DConstraint(model, ct));
1218 break;
1219 default:
1220 LOG(DFATAL) << "Shouldn't be here";
1221 }
1222 }
1223
1224 if (model.has_objective() && model.has_floating_point_objective()) {
1225 return "A model cannot have both an objective and a floating point "
1226 "objective.";
1227 }
1228 if (model.has_objective()) {
1229 if (model.objective().scaling_factor() != 0 &&
1230 !std::isnormal(model.objective().scaling_factor())) {
1231 return "A model cannot have an objective with a nan, inf or subnormal "
1232 "scaling factor";
1233 }
1234
1235 RETURN_IF_NOT_EMPTY(ValidateObjective(model, model.objective()));
1236
1237 if (model.objective().integer_scaling_factor() != 0 ||
1238 model.objective().integer_before_offset() != 0 ||
1239 model.objective().integer_after_offset() != 0) {
1240 // If any of these fields are set, the domain must be set.
1241 if (model.objective().domain().empty()) {
1242 return absl::StrCat(
1243 "Objective integer scaling or offset is set without an objective "
1244 "domain.");
1245 }
1246
1247 // Check that we can transform any value in the objective domain without
1248 // overflow. We only check the bounds which is enough.
1249 bool overflow = false;
1250 for (const int64_t v : model.objective().domain()) {
1251 int64_t t = CapAdd(v, model.objective().integer_before_offset());
1252 if (AtMinOrMaxInt64(t)) {
1253 overflow = true;
1254 break;
1255 }
1256 t = CapProd(t, model.objective().integer_scaling_factor());
1257 if (AtMinOrMaxInt64(t)) {
1258 overflow = true;
1259 break;
1260 }
1261 t = CapAdd(t, model.objective().integer_after_offset());
1262 if (AtMinOrMaxInt64(t)) {
1263 overflow = true;
1264 break;
1265 }
1266 }
1267 if (overflow) {
1268 return absl::StrCat(
1269 "Internal fields related to the postsolve of the integer objective "
1270 "are causing a potential integer overflow: ",
1271 ProtobufShortDebugString(model.objective()));
1272 }
1273 }
1274 }
1275 RETURN_IF_NOT_EMPTY(ValidateSearchStrategies(model));
1276 RETURN_IF_NOT_EMPTY(ValidateSolutionHint(model));
1277 for (const int ref : model.assumptions()) {
1278 if (!LiteralReferenceIsValid(model, ref)) {
1279 return absl::StrCat("Invalid literal reference ", ref,
1280 " in the 'assumptions' field.");
1281 }
1282 }
1283 return "";
1284}
1285
1286std::string ValidateInputCpModel(const SatParameters& params,
1287 const CpModelProto& model) {
1289 if (model.has_floating_point_objective()) {
1291 ValidateFloatingPointObjective(params.mip_max_valid_magnitude(), model,
1292 model.floating_point_objective()));
1293 }
1294 return "";
1295}
1296
1297#undef RETURN_IF_NOT_EMPTY
1298
1299// =============================================================================
1300// Solution Feasibility.
1301// =============================================================================
1302
1303namespace {
1304
1305class ConstraintChecker {
1306 public:
1307 explicit ConstraintChecker(absl::Span<const int64_t> variable_values)
1308 : variable_values_(variable_values.begin(), variable_values.end()) {}
1309
1310 bool LiteralIsTrue(int l) const {
1311 if (l >= 0) return variable_values_[l] != 0;
1312 return variable_values_[-l - 1] == 0;
1313 }
1314
1315 bool LiteralIsFalse(int l) const { return !LiteralIsTrue(l); }
1316
1317 int64_t Value(int var) const {
1318 if (var >= 0) return variable_values_[var];
1319 return -variable_values_[-var - 1];
1320 }
1321
1322 bool ConstraintIsEnforced(const ConstraintProto& ct) {
1323 for (const int lit : ct.enforcement_literal()) {
1324 if (LiteralIsFalse(lit)) return false;
1325 }
1326 return true;
1327 }
1328
1329 bool BoolOrConstraintIsFeasible(const ConstraintProto& ct) {
1330 for (const int lit : ct.bool_or().literals()) {
1331 if (LiteralIsTrue(lit)) return true;
1332 }
1333 return false;
1334 }
1335
1336 bool BoolAndConstraintIsFeasible(const ConstraintProto& ct) {
1337 for (const int lit : ct.bool_and().literals()) {
1338 if (LiteralIsFalse(lit)) return false;
1339 }
1340 return true;
1341 }
1342
1343 bool AtMostOneConstraintIsFeasible(const ConstraintProto& ct) {
1344 int num_true_literals = 0;
1345 for (const int lit : ct.at_most_one().literals()) {
1346 if (LiteralIsTrue(lit)) ++num_true_literals;
1347 }
1348 return num_true_literals <= 1;
1349 }
1350
1351 bool ExactlyOneConstraintIsFeasible(const ConstraintProto& ct) {
1352 int num_true_literals = 0;
1353 for (const int lit : ct.exactly_one().literals()) {
1354 if (LiteralIsTrue(lit)) ++num_true_literals;
1355 }
1356 return num_true_literals == 1;
1357 }
1358
1359 bool BoolXorConstraintIsFeasible(const ConstraintProto& ct) {
1360 int sum = 0;
1361 for (const int lit : ct.bool_xor().literals()) {
1362 sum ^= LiteralIsTrue(lit) ? 1 : 0;
1363 }
1364 return sum == 1;
1365 }
1366
1367 bool LinearConstraintIsFeasible(const ConstraintProto& ct) {
1368 int64_t sum = 0;
1369 const int num_variables = ct.linear().coeffs_size();
1370 absl::Span<const int> vars = absl::MakeSpan(ct.linear().vars());
1371 absl::Span<const int64_t> coeffs = absl::MakeSpan(ct.linear().coeffs());
1372 for (int i = 0; i < num_variables; ++i) {
1373 // We know we only have positive reference now.
1374 DCHECK(RefIsPositive(vars[i]));
1375 sum += variable_values_[vars[i]] * coeffs[i];
1376 }
1377 const bool result = DomainInProtoContains(ct.linear(), sum);
1378 if (!result) {
1379 VLOG(1) << "Activity: " << sum;
1380 }
1381 return result;
1382 }
1383
1384 int64_t LinearExpressionValue(const LinearExpressionProto& expr) const {
1385 int64_t sum = expr.offset();
1386 const int num_variables = expr.vars_size();
1387 for (int i = 0; i < num_variables; ++i) {
1388 sum += Value(expr.vars(i)) * expr.coeffs(i);
1389 }
1390 return sum;
1391 }
1392
1393 bool LinMaxConstraintIsFeasible(const ConstraintProto& ct) {
1394 const int64_t max = LinearExpressionValue(ct.lin_max().target());
1395 int64_t actual_max = std::numeric_limits<int64_t>::min();
1396 for (int i = 0; i < ct.lin_max().exprs_size(); ++i) {
1397 const int64_t expr_value = LinearExpressionValue(ct.lin_max().exprs(i));
1398 actual_max = std::max(actual_max, expr_value);
1399 }
1400 return max == actual_max;
1401 }
1402
1403 bool IntProdConstraintIsFeasible(const ConstraintProto& ct) {
1404 const int64_t prod = LinearExpressionValue(ct.int_prod().target());
1405 int64_t actual_prod = 1;
1406 for (const LinearExpressionProto& expr : ct.int_prod().exprs()) {
1407 actual_prod = CapProd(actual_prod, LinearExpressionValue(expr));
1408 }
1409 return prod == actual_prod;
1410 }
1411
1412 bool IntDivConstraintIsFeasible(const ConstraintProto& ct) {
1413 return LinearExpressionValue(ct.int_div().target()) ==
1414 LinearExpressionValue(ct.int_div().exprs(0)) /
1415 LinearExpressionValue(ct.int_div().exprs(1));
1416 }
1417
1418 bool IntModConstraintIsFeasible(const ConstraintProto& ct) {
1419 return LinearExpressionValue(ct.int_mod().target()) ==
1420 LinearExpressionValue(ct.int_mod().exprs(0)) %
1421 LinearExpressionValue(ct.int_mod().exprs(1));
1422 }
1423
1424 bool AllDiffConstraintIsFeasible(const ConstraintProto& ct) {
1425 absl::flat_hash_set<int64_t> values;
1426 for (const LinearExpressionProto& expr : ct.all_diff().exprs()) {
1427 const int64_t value = LinearExpressionValue(expr);
1428 const auto [it, inserted] = values.insert(value);
1429 if (!inserted) return false;
1430 }
1431 return true;
1432 }
1433
1434 int64_t IntervalStart(const IntervalConstraintProto& interval) const {
1435 return LinearExpressionValue(interval.start());
1436 }
1437
1438 int64_t IntervalSize(const IntervalConstraintProto& interval) const {
1439 return LinearExpressionValue(interval.size());
1440 }
1441
1442 int64_t IntervalEnd(const IntervalConstraintProto& interval) const {
1443 return LinearExpressionValue(interval.end());
1444 }
1445
1446 bool IntervalConstraintIsFeasible(const ConstraintProto& ct) {
1447 const int64_t size = IntervalSize(ct.interval());
1448 if (size < 0) return false;
1449 return IntervalStart(ct.interval()) + size == IntervalEnd(ct.interval());
1450 }
1451
1452 bool NoOverlapConstraintIsFeasible(const CpModelProto& model,
1453 const ConstraintProto& ct) {
1454 std::vector<std::pair<int64_t, int64_t>> start_durations_pairs;
1455 for (const int i : ct.no_overlap().intervals()) {
1456 const ConstraintProto& interval_constraint = model.constraints(i);
1457 if (ConstraintIsEnforced(interval_constraint)) {
1458 const IntervalConstraintProto& interval =
1459 interval_constraint.interval();
1460 start_durations_pairs.push_back(
1461 {IntervalStart(interval), IntervalSize(interval)});
1462 }
1463 }
1464 std::sort(start_durations_pairs.begin(), start_durations_pairs.end());
1465 int64_t previous_end = std::numeric_limits<int64_t>::min();
1466 for (const auto& pair : start_durations_pairs) {
1467 if (pair.first < previous_end) return false;
1468 previous_end = pair.first + pair.second;
1469 }
1470 return true;
1471 }
1472
1473 bool NoOverlap2DConstraintIsFeasible(const CpModelProto& model,
1474 const ConstraintProto& ct) {
1475 const auto& arg = ct.no_overlap_2d();
1476 // Those intervals from arg.x_intervals and arg.y_intervals where both
1477 // the x and y intervals are enforced.
1478 bool has_zero_sizes = false;
1479 std::vector<Rectangle> enforced_rectangles;
1480 {
1481 const int num_intervals = arg.x_intervals_size();
1482 CHECK_EQ(arg.y_intervals_size(), num_intervals);
1483 for (int i = 0; i < num_intervals; ++i) {
1484 const ConstraintProto& x = model.constraints(arg.x_intervals(i));
1485 const ConstraintProto& y = model.constraints(arg.y_intervals(i));
1486 if (ConstraintIsEnforced(x) && ConstraintIsEnforced(y)) {
1487 enforced_rectangles.push_back({.x_min = IntervalStart(x.interval()),
1488 .x_max = IntervalEnd(x.interval()),
1489 .y_min = IntervalStart(y.interval()),
1490 .y_max = IntervalEnd(y.interval())});
1491 const auto& rect = enforced_rectangles.back();
1492 if (rect.x_min == rect.x_max || rect.y_min == rect.y_max) {
1493 has_zero_sizes = true;
1494 }
1495 }
1496 }
1497 }
1498
1499 std::optional<std::pair<int, int>> one_intersection;
1500 absl::c_stable_sort(enforced_rectangles,
1501 [](const Rectangle& a, const Rectangle& b) {
1502 return a.x_min < b.x_min;
1503 });
1504 if (has_zero_sizes) {
1505 one_intersection =
1506 FindOneIntersectionIfPresentWithZeroArea(enforced_rectangles);
1507 } else {
1508 one_intersection = FindOneIntersectionIfPresent(enforced_rectangles);
1509 }
1510
1511 if (one_intersection != std::nullopt) {
1512 VLOG(1) << "Rectangles " << one_intersection->first << "("
1513 << enforced_rectangles[one_intersection->first] << ") and "
1514 << one_intersection->second << "("
1515 << enforced_rectangles[one_intersection->second]
1516 << ") are not disjoint.";
1517 return false;
1518 }
1519 return true;
1520 }
1521
1522 bool CumulativeConstraintIsFeasible(const CpModelProto& model,
1523 const ConstraintProto& ct) {
1524 const int64_t capacity = LinearExpressionValue(ct.cumulative().capacity());
1525 if (capacity < 0) return false;
1526 const int num_intervals = ct.cumulative().intervals_size();
1527 std::vector<std::pair<int64_t, int64_t>> events;
1528 for (int i = 0; i < num_intervals; ++i) {
1529 const ConstraintProto& interval_constraint =
1530 model.constraints(ct.cumulative().intervals(i));
1531 if (!ConstraintIsEnforced(interval_constraint)) continue;
1532 const int64_t start = IntervalStart(interval_constraint.interval());
1533 const int64_t duration = IntervalSize(interval_constraint.interval());
1534 const int64_t demand = LinearExpressionValue(ct.cumulative().demands(i));
1535 if (duration == 0 || demand == 0) continue;
1536 events.emplace_back(start, demand);
1537 events.emplace_back(start + duration, -demand);
1538 }
1539 if (events.empty()) return true;
1540
1541 std::sort(events.begin(), events.end());
1542
1543 // This works because we will process negative demands first.
1544 int64_t current_load = 0;
1545 for (const auto& [time, delta] : events) {
1546 current_load += delta;
1547 if (current_load > capacity) {
1548 VLOG(1) << "Cumulative constraint: load: " << current_load
1549 << " capacity: " << capacity << " time: " << time;
1550 return false;
1551 }
1552 }
1553 DCHECK_EQ(current_load, 0);
1554 return true;
1555 }
1556
1557 bool ElementConstraintIsFeasible(const ConstraintProto& ct) {
1558 if (!ct.element().vars().empty()) {
1559 const int index = Value(ct.element().index());
1560 if (index < 0 || index >= ct.element().vars_size()) return false;
1561 return Value(ct.element().vars(index)) == Value(ct.element().target());
1562 }
1563
1564 if (!ct.element().exprs().empty()) {
1565 const int index = LinearExpressionValue(ct.element().linear_index());
1566 if (index < 0 || index >= ct.element().exprs_size()) return false;
1567 return LinearExpressionValue(ct.element().exprs(index)) ==
1568 LinearExpressionValue(ct.element().linear_target());
1569 }
1570
1571 return false;
1572 }
1573
1574 bool TableConstraintIsFeasible(const ConstraintProto& ct) {
1575 std::vector<int64_t> solution;
1576 if (ct.table().exprs().empty()) {
1577 for (int i = 0; i < ct.table().vars_size(); ++i) {
1578 solution.push_back(Value(ct.table().vars(i)));
1579 }
1580 } else {
1581 for (int i = 0; i < ct.table().exprs_size(); ++i) {
1582 solution.push_back(LinearExpressionValue(ct.table().exprs(i)));
1583 }
1584 }
1585
1586 // No expression -> always feasible.
1587 if (solution.empty()) return true;
1588 const int size = solution.size();
1589
1590 for (int row_start = 0; row_start < ct.table().values_size();
1591 row_start += size) {
1592 int i = 0;
1593 while (solution[i] == ct.table().values(row_start + i)) {
1594 ++i;
1595 if (i == size) return !ct.table().negated();
1596 }
1597 }
1598 return ct.table().negated();
1599 }
1600
1601 bool AutomatonConstraintIsFeasible(const ConstraintProto& ct) {
1602 // Build the transition table {tail, label} -> head.
1603 const AutomatonConstraintProto& automaton = ct.automaton();
1604 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> transition_map;
1605 const int num_transitions = automaton.transition_tail().size();
1606 for (int i = 0; i < num_transitions; ++i) {
1607 transition_map[{automaton.transition_tail(i),
1608 automaton.transition_label(i)}] =
1609 automaton.transition_head(i);
1610 }
1611
1612 // Walk the automaton.
1613 int64_t current_state = automaton.starting_state();
1614 const int num_steps =
1615 std::max(automaton.vars_size(), automaton.exprs_size());
1616 for (int i = 0; i < num_steps; ++i) {
1617 const std::pair<int64_t, int64_t> key = {
1618 current_state, automaton.vars().empty()
1619 ? LinearExpressionValue(automaton.exprs(i))
1620 : Value(automaton.vars(i))};
1621 if (!transition_map.contains(key)) {
1622 return false;
1623 }
1624 current_state = transition_map[key];
1625 }
1626
1627 // Check we are now in a final state.
1628 for (const int64_t final : automaton.final_states()) {
1629 if (current_state == final) return true;
1630 }
1631 return false;
1632 }
1633
1634 bool CircuitConstraintIsFeasible(const ConstraintProto& ct) {
1635 // Compute the set of relevant nodes for the constraint and set the next of
1636 // each of them. This also detects duplicate nexts.
1637 const int num_arcs = ct.circuit().tails_size();
1638 absl::flat_hash_set<int> nodes;
1639 absl::flat_hash_map<int, int> nexts;
1640 for (int i = 0; i < num_arcs; ++i) {
1641 const int tail = ct.circuit().tails(i);
1642 const int head = ct.circuit().heads(i);
1643 nodes.insert(tail);
1644 nodes.insert(head);
1645 if (LiteralIsFalse(ct.circuit().literals(i))) continue;
1646 if (nexts.contains(tail)) {
1647 VLOG(1) << "Node with two outgoing arcs";
1648 return false; // Duplicate.
1649 }
1650 nexts[tail] = head;
1651 }
1652
1653 // All node must have a next.
1654 int in_cycle;
1655 int cycle_size = 0;
1656 for (const int node : nodes) {
1657 if (!nexts.contains(node)) {
1658 VLOG(1) << "Node with no next: " << node;
1659 return false; // No next.
1660 }
1661 if (nexts[node] == node) continue; // skip self-loop.
1662 in_cycle = node;
1663 ++cycle_size;
1664 }
1665 if (cycle_size == 0) return true;
1666
1667 // Check that we have only one cycle. visited is used to not loop forever if
1668 // we have a "rho" shape instead of a cycle.
1669 absl::flat_hash_set<int> visited;
1670 int current = in_cycle;
1671 int num_visited = 0;
1672 while (!visited.contains(current)) {
1673 ++num_visited;
1674 visited.insert(current);
1675 current = nexts[current];
1676 }
1677 if (current != in_cycle) {
1678 VLOG(1) << "Rho shape";
1679 return false; // Rho shape.
1680 }
1681 if (num_visited != cycle_size) {
1682 VLOG(1) << "More than one cycle";
1683 }
1684 return num_visited == cycle_size; // Another cycle somewhere if false.
1685 }
1686
1687 bool RoutesConstraintIsFeasible(const ConstraintProto& ct) {
1688 const int num_arcs = ct.routes().tails_size();
1689 int num_used_arcs = 0;
1690 int num_self_arcs = 0;
1691
1692 // Compute the number of nodes.
1693 int num_nodes = 0;
1694 for (int i = 0; i < num_arcs; ++i) {
1695 num_nodes = std::max(num_nodes, 1 + ct.routes().tails(i));
1696 num_nodes = std::max(num_nodes, 1 + ct.routes().heads(i));
1697 }
1698
1699 std::vector<int> tail_to_head(num_nodes, -1);
1700 std::vector<bool> has_incoming_arc(num_nodes, false);
1701 std::vector<int> has_outgoing_arc(num_nodes, false);
1702 std::vector<int> depot_nexts;
1703 for (int i = 0; i < num_arcs; ++i) {
1704 const int tail = ct.routes().tails(i);
1705 const int head = ct.routes().heads(i);
1706 if (LiteralIsTrue(ct.routes().literals(i))) {
1707 // Check for loops.
1708 if (tail != 0) {
1709 if (has_outgoing_arc[tail]) {
1710 VLOG(1) << "routes: node " << tail << "has two outgoing arcs";
1711 return false;
1712 }
1713 has_outgoing_arc[tail] = true;
1714 }
1715 if (head != 0) {
1716 if (has_incoming_arc[head]) {
1717 VLOG(1) << "routes: node " << head << "has two incoming arcs";
1718 return false;
1719 }
1720 has_incoming_arc[head] = true;
1721 }
1722
1723 if (tail == head) {
1724 if (tail == 0) {
1725 VLOG(1) << "Self loop on node 0 are forbidden.";
1726 return false;
1727 }
1728 ++num_self_arcs;
1729 continue;
1730 }
1731 ++num_used_arcs;
1732 if (tail == 0) {
1733 depot_nexts.push_back(head);
1734 } else {
1735 DCHECK_EQ(tail_to_head[tail], -1);
1736 tail_to_head[tail] = head;
1737 }
1738 }
1739 }
1740
1741 // An empty constraint with no node to visit should be feasible.
1742 if (num_nodes == 0) return true;
1743
1744 // Make sure each routes from the depot go back to it, and count such arcs.
1745 int count = 0;
1746 for (int start : depot_nexts) {
1747 ++count;
1748 while (start != 0) {
1749 if (tail_to_head[start] == -1) return false;
1750 start = tail_to_head[start];
1751 ++count;
1752 }
1753 }
1754
1755 if (count != num_used_arcs) {
1756 VLOG(1) << "count: " << count << " != num_used_arcs:" << num_used_arcs;
1757 return false;
1758 }
1759
1760 // Each routes cover as many node as there is arcs, but this way we count
1761 // multiple time_exprs the depot. So the number of nodes covered are:
1762 // count - depot_nexts.size() + 1.
1763 // And this number + the self arcs should be num_nodes.
1764 if (count - depot_nexts.size() + 1 + num_self_arcs != num_nodes) {
1765 VLOG(1) << "Not all nodes are covered!";
1766 return false;
1767 }
1768
1769 return true;
1770 }
1771
1772 bool InverseConstraintIsFeasible(const ConstraintProto& ct) {
1773 const int num_variables = ct.inverse().f_direct_size();
1774 if (num_variables != ct.inverse().f_inverse_size()) return false;
1775 // Check that f_inverse(f_direct(i)) == i; this is sufficient.
1776 for (int i = 0; i < num_variables; i++) {
1777 const int fi = Value(ct.inverse().f_direct(i));
1778 if (fi < 0 || num_variables <= fi) return false;
1779 if (i != Value(ct.inverse().f_inverse(fi))) return false;
1780 }
1781 return true;
1782 }
1783
1784 bool ReservoirConstraintIsFeasible(const ConstraintProto& ct) {
1785 const int num_variables = ct.reservoir().time_exprs_size();
1786 const int64_t min_level = ct.reservoir().min_level();
1787 const int64_t max_level = ct.reservoir().max_level();
1788 absl::btree_map<int64_t, int64_t> deltas;
1789 const bool has_active_variables = ct.reservoir().active_literals_size() > 0;
1790 for (int i = 0; i < num_variables; i++) {
1791 const int64_t time = LinearExpressionValue(ct.reservoir().time_exprs(i));
1792 if (!has_active_variables ||
1793 Value(ct.reservoir().active_literals(i)) == 1) {
1794 const int64_t level =
1795 LinearExpressionValue(ct.reservoir().level_changes(i));
1796 deltas[time] += level;
1797 }
1798 }
1799 int64_t current_level = 0;
1800 for (const auto& delta : deltas) {
1801 current_level += delta.second;
1802 if (current_level < min_level || current_level > max_level) {
1803 VLOG(1) << "Reservoir level " << current_level
1804 << " is out of bounds at time: " << delta.first;
1805 return false;
1806 }
1807 }
1808 return true;
1809 }
1810
1811 bool ConstraintIsFeasible(const CpModelProto& model,
1812 const ConstraintProto& ct) {
1813 // A non-enforced constraint is always feasible.
1814 if (!ConstraintIsEnforced(ct)) return true;
1815
1816 const ConstraintProto::ConstraintCase type = ct.constraint_case();
1817 switch (type) {
1819 return BoolOrConstraintIsFeasible(ct);
1821 return BoolAndConstraintIsFeasible(ct);
1823 return AtMostOneConstraintIsFeasible(ct);
1825 return ExactlyOneConstraintIsFeasible(ct);
1827 return BoolXorConstraintIsFeasible(ct);
1829 return LinearConstraintIsFeasible(ct);
1831 return IntProdConstraintIsFeasible(ct);
1833 return IntDivConstraintIsFeasible(ct);
1835 return IntModConstraintIsFeasible(ct);
1837 return LinMaxConstraintIsFeasible(ct);
1839 return AllDiffConstraintIsFeasible(ct);
1841 if (!IntervalConstraintIsFeasible(ct)) {
1842 if (ct.interval().has_start()) {
1843 // Tricky: For simplified presolve, we require that a separate
1844 // constraint is added to the model to enforce the "interval".
1845 // This indicates that such a constraint was not added to the model.
1846 // It should probably be a validation error, but it is hard to
1847 // detect beforehand.
1848 VLOG(1) << "Warning, an interval constraint was likely used "
1849 "without a corresponding linear constraint linking "
1850 "its start, size and end.";
1851 }
1852 return false;
1853 }
1854 return true;
1856 return NoOverlapConstraintIsFeasible(model, ct);
1858 return NoOverlap2DConstraintIsFeasible(model, ct);
1860 return CumulativeConstraintIsFeasible(model, ct);
1862 return ElementConstraintIsFeasible(ct);
1864 return TableConstraintIsFeasible(ct);
1866 return AutomatonConstraintIsFeasible(ct);
1868 return CircuitConstraintIsFeasible(ct);
1870 return RoutesConstraintIsFeasible(ct);
1872 return InverseConstraintIsFeasible(ct);
1874 return ReservoirConstraintIsFeasible(ct);
1876 // Empty constraint is always feasible.
1877 return true;
1878 default:
1879 LOG(FATAL) << "Unuspported constraint: " << ConstraintCaseName(type);
1880 return false;
1881 }
1882 }
1883
1884 private:
1885 const std::vector<int64_t> variable_values_;
1886};
1887
1888} // namespace
1889
1890bool ConstraintIsFeasible(const CpModelProto& model,
1891 const ConstraintProto& constraint,
1892 absl::Span<const int64_t> variable_values) {
1893 ConstraintChecker checker(variable_values);
1894 return checker.ConstraintIsFeasible(model, constraint);
1895}
1896
1897bool SolutionIsFeasible(const CpModelProto& model,
1898 absl::Span<const int64_t> variable_values,
1899 const CpModelProto* mapping_proto,
1900 const std::vector<int>* postsolve_mapping) {
1901 if (variable_values.size() != model.variables_size()) {
1902 VLOG(1) << "Wrong number of variables (" << variable_values.size()
1903 << ") in the solution vector. It should be "
1904 << model.variables_size() << ".";
1905 return false;
1906 }
1907
1908 // Check that all values fall in the variable domains.
1909 for (int i = 0; i < model.variables_size(); ++i) {
1910 if (!DomainInProtoContains(model.variables(i), variable_values[i])) {
1911 VLOG(1) << "Variable #" << i << " has value " << variable_values[i]
1912 << " which do not fall in its domain: "
1913 << ProtobufShortDebugString(model.variables(i));
1914 return false;
1915 }
1916 }
1917
1918 CHECK_EQ(variable_values.size(), model.variables_size());
1919 ConstraintChecker checker(variable_values);
1920
1921 for (int c = 0; c < model.constraints_size(); ++c) {
1922 const ConstraintProto& ct = model.constraints(c);
1923 if (checker.ConstraintIsFeasible(model, ct)) continue;
1924
1925 // Display a message to help debugging.
1926 VLOG(1) << "Failing constraint #" << c << " : "
1927 << ProtobufShortDebugString(model.constraints(c));
1928 if (mapping_proto != nullptr && postsolve_mapping != nullptr) {
1929 std::vector<int> reverse_map(mapping_proto->variables().size(), -1);
1930 for (int var = 0; var < postsolve_mapping->size(); ++var) {
1931 reverse_map[(*postsolve_mapping)[var]] = var;
1932 }
1933 for (const int var : UsedVariables(model.constraints(c))) {
1934 VLOG(1) << "var: " << var << " mapped_to: " << reverse_map[var]
1935 << " value: " << variable_values[var] << " initial_domain: "
1936 << ReadDomainFromProto(model.variables(var))
1937 << " postsolved_domain: "
1938 << ReadDomainFromProto(mapping_proto->variables(var));
1939 }
1940 } else {
1941 for (const int var : UsedVariables(model.constraints(c))) {
1942 VLOG(1) << "var: " << var << " value: " << variable_values[var];
1943 }
1944 }
1945 return false;
1946 }
1947
1948 // Check that the objective is within its domain.
1949 //
1950 // TODO(user): This is not really a "feasibility" question, but we should
1951 // probably check that the response objective matches with the one we can
1952 // compute here. This might better be done in another function though.
1953 if (model.has_objective()) {
1954 int64_t inner_objective = 0;
1955 const int num_variables = model.objective().coeffs_size();
1956 for (int i = 0; i < num_variables; ++i) {
1957 inner_objective += checker.Value(model.objective().vars(i)) *
1958 model.objective().coeffs(i);
1959 }
1960 if (!model.objective().domain().empty()) {
1961 if (!DomainInProtoContains(model.objective(), inner_objective)) {
1962 VLOG(1) << "Objective value " << inner_objective << " not in domain! "
1963 << ReadDomainFromProto(model.objective());
1964 return false;
1965 }
1966 }
1967 double factor = model.objective().scaling_factor();
1968 if (factor == 0.0) factor = 1.0;
1969 const double scaled_objective =
1970 factor *
1971 (static_cast<double>(inner_objective) + model.objective().offset());
1972 VLOG(2) << "Checker inner objective = " << inner_objective;
1973 VLOG(2) << "Checker scaled objective = " << scaled_objective;
1974 }
1975
1976 return true;
1977}
1978
1979bool SolutionCanBeOptimal(const CpModelProto& model,
1980 absl::Span<const int64_t> variable_values) {
1981 if (absl::GetFlag(FLAGS_cp_model_check_dependent_variables)) {
1982 const VariableRelationships relationships =
1984 std::vector<int64_t> all_variables(variable_values.begin(),
1985 variable_values.end());
1986 for (const int var : relationships.secondary_variables) {
1987 all_variables[var] = -999999; // Those values should be overwritten.
1988 }
1989 CHECK(ComputeAllVariablesFromPrimaryVariables(model, relationships,
1990 &all_variables));
1991 CHECK(absl::MakeSpan(all_variables) == variable_values);
1992 }
1993 return true;
1994}
1995
1996} // namespace sat
1997} // namespace operations_research
static constexpr DomainReductionStrategy SELECT_MAX_VALUE
static constexpr DomainReductionStrategy SELECT_MIN_VALUE
static constexpr VariableSelectionStrategy CHOOSE_HIGHEST_MAX
static constexpr VariableSelectionStrategy CHOOSE_FIRST
static constexpr DomainReductionStrategy SELECT_MEDIAN_VALUE
static constexpr DomainReductionStrategy SELECT_UPPER_HALF
static constexpr VariableSelectionStrategy CHOOSE_LOWEST_MIN
static constexpr VariableSelectionStrategy CHOOSE_MAX_DOMAIN_SIZE
static constexpr VariableSelectionStrategy CHOOSE_MIN_DOMAIN_SIZE
static constexpr DomainReductionStrategy SELECT_LOWER_HALF
static constexpr DomainReductionStrategy SELECT_RANDOM_HALF
RoutesConstraintProto_NodeExpressions NodeExpressions
#define RETURN_IF_NOT_EMPTY(statement)
ABSL_FLAG(bool, cp_model_check_dependent_variables, false, "When true, check that solutions can be computed only from their " "free variables.")
absl::Status ValidateLinearExpression(const LinearExpressionProto &expression, const IdNameBiMap &variable_universe)
std::string ValidateInputCpModel(const SatParameters &params, const CpModelProto &model)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
bool SolutionIsFeasible(const CpModelProto &model, absl::Span< const int64_t > variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
bool SolutionCanBeOptimal(const CpModelProto &model, absl::Span< const int64_t > variable_values)
bool ConstraintIsFeasible(const CpModelProto &model, const ConstraintProto &constraint, absl::Span< const int64_t > variable_values)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
std::optional< std::pair< int, int > > FindOneIntersectionIfPresent(absl::Span< const Rectangle > rectangles)
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset, std::pair< int64_t, int64_t > *implied_domain)
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition integer.h:1839
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
absl::string_view ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
std::optional< std::pair< int, int > > FindOneIntersectionIfPresentWithZeroArea(absl::Span< const Rectangle > rectangles)
bool ComputeAllVariablesFromPrimaryVariables(const CpModelProto &model, const VariableRelationships &relationships, std::vector< int64_t > *solution)
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)
VariableRelationships ComputeVariableRelationships(const CpModelProto &model)
OR-Tools root namespace.
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
int64_t CapSub(int64_t x, int64_t y)
ClosedInterval::Iterator end(ClosedInterval interval)
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:46
int64_t CapProd(int64_t x, int64_t y)
int64_t CapAbs(int64_t v)
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
std::string ProtobufDebugString(const P &message)
Definition proto_utils.h:31
ClosedInterval::Iterator begin(ClosedInterval interval)
static int input(yyscan_t yyscanner)