22#include "absl/container/flat_hash_map.h"
23#include "absl/container/flat_hash_set.h"
24#include "absl/strings/str_cat.h"
43#define RETURN_IF_NOT_EMPTY(statement) \
45 const std::string error_message = statement; \
46 if (!error_message.empty()) return error_message; \
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)});
60bool VariableReferenceIsValid(
const CpModelProto&
model,
int reference) {
62 if (reference >=
model.variables_size())
return false;
63 return reference >= -
static_cast<int>(
model.variables_size());
70bool VariableIndexIsValid(
const CpModelProto&
model,
int var) {
74bool LiteralReferenceIsValid(
const CpModelProto&
model,
int reference) {
75 if (!VariableReferenceIsValid(
model, reference))
return false;
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;
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,
88 if (
proto.domain_size() % 2 != 0) {
89 return absl::StrCat(
"var #", v,
" has an odd domain() size: ",
92 if (!DomainInProtoIsValid(
proto)) {
93 return absl::StrCat(
"var #", v,
" has and invalid domain() format: ",
100 const int64_t lb =
proto.domain(0);
101 const int64_t ub =
proto.domain(
proto.domain_size() - 1);
105 "var #", v,
" domain do not fall in [kint64min + 2, kint64max - 1]. ",
114 " has a domain that is too large, i.e. |UB - LB| overflow an int64_t: ",
121std::string ValidateVariablesUsedInConstraint(
const CpModelProto&
model,
123 const ConstraintProto&
ct =
model.constraints(c);
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,
" : ",
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,
" : ",
139 for (
const int lit : references.literals) {
140 if (!LiteralReferenceIsValid(
model, lit)) {
141 return absl::StrCat(
"Invalid literal ", lit,
" in constraint #", c,
" : ",
148std::string ValidateIntervalsUsedInConstraint(
bool after_presolve,
149 const CpModelProto&
model,
151 const ConstraintProto&
ct =
model.constraints(c);
153 if (i < 0 || i >=
model.constraints_size()) {
154 return absl::StrCat(
"Out of bound interval ", i,
" in constraint #", c,
157 if (after_presolve && i >= c) {
158 return absl::StrCat(
"Interval ", i,
" in constraint #", c,
159 " must appear before in the list of constraints :",
162 if (
model.constraints(i).constraint_case() !=
163 ConstraintProto::ConstraintCase::kInterval) {
166 " does not refer to an interval constraint. Problematic constraint #",
173template <
class LinearExpressionProto>
174bool PossibleIntegerOverflow(
const CpModelProto&
model,
175 const LinearExpressionProto&
proto,
176 int64_t offset = 0) {
178 int64_t sum_min = -std::abs(offset);
179 int64_t sum_max = +std::abs(offset);
180 for (
int i = 0; i <
proto.vars_size(); ++i) {
181 const int ref =
proto.vars(i);
183 const int64_t min_domain = var_proto.domain(0);
184 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
186 const int64_t
coeff =
196 for (
const int64_t v : {prod1, prod2, sum_min, sum_max}) {
211int64_t MinOfRef(
const CpModelProto&
model,
int ref) {
214 return var_proto.domain(0);
216 return -var_proto.domain(var_proto.domain_size() - 1);
220int64_t MaxOfRef(
const CpModelProto&
model,
int ref) {
223 return var_proto.domain(var_proto.domain_size() - 1);
225 return -var_proto.domain(0);
229template <
class LinearExpressionProto>
230int64_t MinOfExpression(
const CpModelProto&
model,
231 const LinearExpressionProto&
proto) {
232 int64_t sum_min =
proto.offset();
233 for (
int i = 0; i <
proto.vars_size(); ++i) {
234 const int ref =
proto.vars(i);
244template <
class LinearExpressionProto>
245int64_t MaxOfExpression(
const CpModelProto&
model,
246 const LinearExpressionProto&
proto) {
247 int64_t sum_max =
proto.offset();
248 for (
int i = 0; i <
proto.vars_size(); ++i) {
249 const int ref =
proto.vars(i);
259int64_t IntervalSizeMin(
const CpModelProto&
model,
int interval_index) {
260 DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
261 model.constraints(interval_index).constraint_case());
262 const IntervalConstraintProto&
proto =
263 model.constraints(interval_index).interval();
267int64_t IntervalSizeMax(
const CpModelProto&
model,
int interval_index) {
268 DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
269 model.constraints(interval_index).constraint_case());
270 const IntervalConstraintProto&
proto =
271 model.constraints(interval_index).interval();
275Domain DomainOfRef(
const CpModelProto&
model,
int ref) {
280std::string ValidateLinearExpression(
const CpModelProto&
model,
281 const LinearExpressionProto& expr) {
282 if (expr.coeffs_size() != expr.vars_size()) {
283 return absl::StrCat(
"coeffs_size() != vars_size() in linear expression: ",
286 if (PossibleIntegerOverflow(
model, expr, expr.offset())) {
287 return absl::StrCat(
"Possible overflow in linear expression: ",
293std::string ValidateAffineExpression(
const CpModelProto&
model,
294 const LinearExpressionProto& expr) {
295 if (expr.vars_size() > 1) {
296 return absl::StrCat(
"expression must be affine: ",
299 return ValidateLinearExpression(
model, expr);
302std::string ValidateLinearConstraint(
const CpModelProto&
model,
303 const ConstraintProto&
ct) {
304 if (!DomainInProtoIsValid(
ct.linear())) {
305 return absl::StrCat(
"Invalid domain in constraint : ",
308 if (
ct.linear().coeffs_size() !=
ct.linear().vars_size()) {
309 return absl::StrCat(
"coeffs_size() != vars_size() in constraint: ",
312 const LinearConstraintProto& arg =
ct.linear();
313 if (PossibleIntegerOverflow(
model, arg)) {
314 return "Possible integer overflow in constraint: " +
320std::string ValidateIntModConstraint(
const CpModelProto&
model,
321 const ConstraintProto&
ct) {
322 if (
ct.int_mod().exprs().size() != 2) {
323 return absl::StrCat(
"An int_mod constraint should have exactly 2 terms: ",
326 if (!
ct.int_mod().has_target()) {
327 return absl::StrCat(
"An int_mod constraint should have a target: ",
335 const LinearExpressionProto mod_expr =
ct.int_mod().exprs(1);
336 if (MinOfExpression(
model, mod_expr) <= 0) {
338 "An int_mod must have a strictly positive modulo argument: ",
345std::string ValidateIntProdConstraint(
const CpModelProto&
model,
346 const ConstraintProto&
ct) {
347 if (
ct.int_prod().exprs().size() != 2) {
348 return absl::StrCat(
"An int_prod constraint should have exactly 2 terms: ",
351 if (!
ct.int_prod().has_target()) {
352 return absl::StrCat(
"An int_prod constraint should have a target: ",
361 const LinearExpressionProto& expr0 =
ct.int_prod().exprs(0);
362 const LinearExpressionProto& expr1 =
ct.int_prod().exprs(1);
363 const Domain product_domain =
364 Domain({MinOfExpression(
model, expr0), MaxOfExpression(
model, expr0)})
365 .ContinuousMultiplicationBy(Domain(
366 {MinOfExpression(
model, expr1), MaxOfExpression(
model, expr1)}));
368 product_domain.Min() < 0) ||
370 product_domain.Max() > 0)) {
371 return absl::StrCat(
"Potential integer overflow in constraint: ",
377std::string ValidateIntDivConstraint(
const CpModelProto&
model,
378 const ConstraintProto&
ct) {
379 if (
ct.int_div().exprs().size() != 2) {
380 return absl::StrCat(
"An int_div constraint should have exactly 2 terms: ",
383 if (!
ct.int_div().has_target()) {
384 return absl::StrCat(
"An int_div constraint should have a target: ",
392 const LinearExpressionProto& divisor_proto =
ct.int_div().exprs(1);
393 if (MinOfExpression(
model, divisor_proto) <= 0 &&
394 MaxOfExpression(
model, divisor_proto) >= 0) {
395 return absl::StrCat(
"The divisor cannot span across zero in constraint: ",
402std::string ValidateTableConstraint(
const CpModelProto&
model,
403 const ConstraintProto&
ct) {
404 const TableConstraintProto& arg =
ct.table();
405 if (arg.vars().empty())
return "";
406 if (arg.values().size() % arg.vars().size() != 0) {
408 "The flat encoding of a table constraint must be a multiple of the "
409 "number of variable: ",
415std::string ValidateAutomatonConstraint(
const CpModelProto&
model,
416 const ConstraintProto&
ct) {
417 const int num_transistions =
ct.automaton().transition_tail().size();
418 if (num_transistions !=
ct.automaton().transition_head().size() ||
419 num_transistions !=
ct.automaton().transition_label().size()) {
421 "The transitions repeated fields must have the same size: ",
424 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> tail_label_to_head;
425 for (
int i = 0; i < num_transistions; ++i) {
426 const int64_t
tail =
ct.automaton().transition_tail(i);
427 const int64_t
head =
ct.automaton().transition_head(i);
428 const int64_t label =
ct.automaton().transition_label(i);
429 const auto [it, inserted] =
430 tail_label_to_head.insert({{
tail, label},
head});
432 if (it->second ==
head) {
433 return absl::StrCat(
"automaton: duplicate transition ",
tail,
" --(",
434 label,
")--> ",
head);
436 return absl::StrCat(
"automaton: incompatible transitions ",
tail,
437 " --(", label,
")--> ",
head,
" and ",
tail,
" --(",
438 label,
")--> ", it->second);
445template <
typename GraphProto>
446std::string ValidateGraphInput(
bool is_route,
const CpModelProto&
model,
447 const GraphProto& graph) {
448 const int size = graph.tails().size();
449 if (graph.heads().size() != size || graph.literals().size() != size) {
450 return absl::StrCat(
"Wrong field sizes in graph: ",
455 absl::flat_hash_set<int> self_loops;
456 for (
int i = 0; i < size; ++i) {
457 if (graph.heads(i) != graph.tails(i))
continue;
458 if (!self_loops.insert(graph.heads(i)).second) {
460 "Circuit/Route constraint contains multiple self-loop involving "
464 if (is_route && graph.tails(i) == 0) {
466 "A route constraint cannot have a self-loop on the depot (node 0)");
473std::string ValidateRoutesConstraint(
const CpModelProto&
model,
474 const ConstraintProto&
ct) {
476 absl::flat_hash_set<int>
nodes;
477 for (
const int node :
ct.routes().tails()) {
479 return "All node in a route constraint must be in [0, num_nodes)";
482 max_node =
std::max(max_node, node);
484 for (
const int node :
ct.routes().heads()) {
486 return "All node in a route constraint must be in [0, num_nodes)";
489 max_node =
std::max(max_node, node);
491 if (!
nodes.empty() && max_node !=
nodes.size() - 1) {
493 "All nodes in a route constraint must have incident arcs");
496 return ValidateGraphInput(
true,
model,
ct.routes());
499std::string ValidateDomainIsPositive(
const CpModelProto&
model,
int ref,
500 const std::string& ref_name) {
502 const IntegerVariableProto& var_proto =
model.variables(
NegatedRef(ref));
503 if (var_proto.domain(var_proto.domain_size() - 1) > 0) {
504 return absl::StrCat(
"Negative value in ", ref_name,
505 " domain: negation of ",
509 const IntegerVariableProto& var_proto =
model.variables(ref);
510 if (var_proto.domain(0) < 0) {
511 return absl::StrCat(
"Negative value in ", ref_name,
518void AppendToOverflowValidator(
const LinearExpressionProto&
input,
519 LinearExpressionProto* output) {
520 output->mutable_vars()->Add(
input.vars().begin(),
input.vars().end());
521 output->mutable_coeffs()->Add(
input.coeffs().begin(),
input.coeffs().end());
526 CapAdd(std::abs(output->offset()), std::abs(
input.offset())));
529std::string ValidateIntervalConstraint(
const CpModelProto&
model,
530 const ConstraintProto&
ct) {
531 if (
ct.enforcement_literal().size() > 1) {
533 "Interval with more than one enforcement literals are currently not "
537 const IntervalConstraintProto& arg =
ct.interval();
539 if (!arg.has_start()) {
540 return absl::StrCat(
"Interval must have a start expression: ",
543 if (!arg.has_size()) {
544 return absl::StrCat(
"Interval must have a size expression: ",
547 if (!arg.has_end()) {
548 return absl::StrCat(
"Interval must have a end expression: ",
552 LinearExpressionProto for_overflow_validation;
553 if (arg.start().vars_size() > 1) {
554 return "Interval with a start expression containing more than one "
555 "variable are currently not supported.";
558 AppendToOverflowValidator(arg.start(), &for_overflow_validation);
559 if (arg.size().vars_size() > 1) {
560 return "Interval with a size expression containing more than one "
561 "variable are currently not supported.";
564 if (
ct.enforcement_literal().empty() &&
565 MinOfExpression(
model, arg.size()) < 0) {
567 "The size of an performed interval must be >= 0 in constraint: ",
570 AppendToOverflowValidator(arg.size(), &for_overflow_validation);
571 if (arg.end().vars_size() > 1) {
572 return "Interval with a end expression containing more than one "
573 "variable are currently not supported.";
576 AppendToOverflowValidator(arg.end(), &for_overflow_validation);
578 if (PossibleIntegerOverflow(
model, for_overflow_validation,
579 for_overflow_validation.offset())) {
580 return absl::StrCat(
"Possible overflow in interval: ",
587std::string ValidateCumulativeConstraint(
const CpModelProto&
model,
588 const ConstraintProto&
ct) {
589 if (
ct.cumulative().intervals_size() !=
ct.cumulative().demands_size()) {
590 return absl::StrCat(
"intervals_size() != demands_size() in constraint: ",
595 ValidateLinearExpression(
model,
ct.cumulative().capacity()));
596 for (
const LinearExpressionProto&
demand :
ct.cumulative().demands()) {
600 for (
const LinearExpressionProto& demand_expr :
ct.cumulative().demands()) {
601 if (MinOfExpression(
model, demand_expr) < 0) {
603 "Demand ", demand_expr.DebugString(),
606 if (demand_expr.vars_size() > 1) {
607 return absl::StrCat(
"Demand ", demand_expr.DebugString(),
608 " must be affine or constant in constraint: ",
612 if (
ct.cumulative().capacity().vars_size() > 1) {
614 "capacity ",
ct.cumulative().capacity().DebugString(),
618 int64_t sum_max_demands = 0;
619 for (
const LinearExpressionProto& demand_expr :
ct.cumulative().demands()) {
620 const int64_t demand_max = MaxOfExpression(
model, demand_expr);
622 sum_max_demands =
CapAdd(sum_max_demands, demand_max);
624 return "The sum of max demands do not fit on an int64_t in constraint: " +
632std::string ValidateNoOverlap2DConstraint(
const CpModelProto&
model,
633 const ConstraintProto&
ct) {
634 const int size_x =
ct.no_overlap_2d().x_intervals().size();
635 const int size_y =
ct.no_overlap_2d().y_intervals().size();
636 if (size_x != size_y) {
637 return absl::StrCat(
"The two lists of intervals must have the same size: ",
642 int64_t sum_max_areas = 0;
643 for (
int i = 0; i <
ct.no_overlap_2d().x_intervals().size(); ++i) {
644 const int64_t max_size_x =
645 IntervalSizeMax(
model,
ct.no_overlap_2d().x_intervals(i));
646 const int64_t max_size_y =
647 IntervalSizeMax(
model,
ct.no_overlap_2d().y_intervals(i));
648 sum_max_areas =
CapAdd(sum_max_areas,
CapProd(max_size_x, max_size_y));
650 return "Integer overflow when summing all areas in "
658std::string ValidateReservoirConstraint(
const CpModelProto&
model,
659 const ConstraintProto&
ct) {
660 if (
ct.enforcement_literal_size() > 0) {
661 return "Reservoir does not support enforcement literals.";
663 if (
ct.reservoir().time_exprs().size() !=
664 ct.reservoir().level_changes().size()) {
666 "time_exprs and level_changes fields must be of the same size: ",
669 for (
const LinearExpressionProto& expr :
ct.reservoir().time_exprs()) {
672 if (
ct.reservoir().min_level() > 0) {
674 "The min level of a reservoir must be <= 0. Please use fixed events to "
675 "setup initial state: ",
678 if (
ct.reservoir().max_level() < 0) {
680 "The max level of a reservoir must be >= 0. Please use fixed events to "
681 "setup initial state: ",
686 for (
const int64_t
demand :
ct.reservoir().level_changes()) {
689 return "Possible integer overflow in constraint: " +
694 return "Possible integer overflow in constraint: " +
698 if (
ct.reservoir().active_literals_size() > 0 &&
699 ct.reservoir().active_literals_size() !=
700 ct.reservoir().time_exprs_size()) {
701 return "Wrong array length of active_literals variables";
703 if (
ct.reservoir().level_changes_size() > 0 &&
704 ct.reservoir().level_changes_size() !=
ct.reservoir().time_exprs_size()) {
705 return "Wrong array length of level_changes variables";
710std::string ValidateObjective(
const CpModelProto&
model,
711 const CpObjectiveProto& obj) {
712 if (!DomainInProtoIsValid(obj)) {
713 return absl::StrCat(
"The objective has and invalid domain() format: ",
716 if (obj.vars().size() != obj.coeffs().size()) {
717 return absl::StrCat(
"vars and coeffs size do not match in objective: ",
720 for (
const int v : obj.vars()) {
721 if (!VariableReferenceIsValid(
model, v)) {
722 return absl::StrCat(
"Out of bound integer variable ", v,
726 if (PossibleIntegerOverflow(
model, obj)) {
727 return "Possible integer overflow in objective: " +
733std::string ValidateFloatingPointObjective(
const CpModelProto&
model,
734 const FloatObjectiveProto& obj) {
735 if (obj.vars().size() != obj.coeffs().size()) {
736 return absl::StrCat(
"vars and coeffs size do not match in objective: ",
739 for (
const int v : obj.vars()) {
740 if (!VariableIndexIsValid(
model, v)) {
741 return absl::StrCat(
"Out of bound integer variable ", v,
745 for (
const double coef : obj.coeffs()) {
746 if (!std::isfinite(
coef)) {
747 return absl::StrCat(
"Coefficients must be finites in objective: ",
751 if (!std::isfinite(obj.offset())) {
752 return absl::StrCat(
"Offset must be finite in objective: ",
758std::string ValidateSearchStrategies(
const CpModelProto&
model) {
759 for (
const DecisionStrategyProto& strategy :
model.search_strategy()) {
760 const int vss = strategy.variable_selection_strategy();
767 "Unknown or unsupported variable_selection_strategy: ", vss);
769 const int drs = strategy.domain_reduction_strategy();
775 return absl::StrCat(
"Unknown or unsupported domain_reduction_strategy: ",
778 for (
const int ref : strategy.variables()) {
779 if (!VariableReferenceIsValid(
model, ref)) {
780 return absl::StrCat(
"Invalid variable reference in strategy: ",
786 return absl::StrCat(
"Variable #",
PositiveRef(ref),
787 " has a domain too large to be used in a"
788 " SELECT_MEDIAN_VALUE value selection strategy");
791 int previous_index = -1;
792 for (
const auto& transformation : strategy.transformations()) {
793 if (transformation.positive_coeff() <= 0) {
794 return absl::StrCat(
"Affine transformation coeff should be positive: ",
797 if (transformation.index() <= previous_index ||
798 transformation.index() >= strategy.variables_size()) {
800 "Invalid indices (must be sorted and valid) in transformation: ",
803 previous_index = transformation.index();
809std::string ValidateSolutionHint(
const CpModelProto&
model) {
810 if (!
model.has_solution_hint())
return "";
811 const auto& hint =
model.solution_hint();
812 if (hint.vars().size() != hint.values().size()) {
813 return "Invalid solution hint: vars and values do not have the same size.";
815 for (
const int ref : hint.vars()) {
816 if (!VariableReferenceIsValid(
model, ref)) {
817 return absl::StrCat(
"Invalid variable reference in solution hint: ", ref);
822 absl::flat_hash_set<int> indices;
823 for (
const int var : hint.vars()) {
825 if (!insert.second) {
827 "The solution hint contains duplicate variables like the variable "
834 for (
const int64_t
value : hint.values()) {
837 return "The solution hint cannot contains the INT_MIN or INT_MAX values.";
847 for (
int v = 0; v <
model.variables_size(); ++v) {
853 std::vector<int> constraints_using_intervals;
855 for (
int c = 0; c <
model.constraints_size(); ++c) {
860 bool support_enforcement =
false;
864 switch (
ct.constraint_case()) {
865 case ConstraintProto::ConstraintCase::kBoolOr:
866 support_enforcement =
true;
868 case ConstraintProto::ConstraintCase::kBoolAnd:
869 support_enforcement =
true;
871 case ConstraintProto::ConstraintCase::kLinear:
872 support_enforcement =
true;
875 case ConstraintProto::ConstraintCase::kLinMax: {
877 ValidateLinearExpression(
model,
ct.lin_max().target()));
883 case ConstraintProto::ConstraintCase::kIntProd:
886 case ConstraintProto::ConstraintCase::kIntDiv:
889 case ConstraintProto::ConstraintCase::kIntMod:
892 case ConstraintProto::ConstraintCase::kInverse:
893 if (
ct.inverse().f_direct().size() !=
ct.inverse().f_inverse().size()) {
894 return absl::StrCat(
"Non-matching fields size in inverse: ",
898 case ConstraintProto::ConstraintCase::kAllDiff:
903 case ConstraintProto::ConstraintCase::kTable:
906 case ConstraintProto::ConstraintCase::kAutomaton:
909 case ConstraintProto::ConstraintCase::kCircuit:
911 ValidateGraphInput(
false,
model,
ct.circuit()));
913 case ConstraintProto::ConstraintCase::kRoutes:
916 case ConstraintProto::ConstraintCase::kInterval:
918 support_enforcement =
true;
920 case ConstraintProto::ConstraintCase::kCumulative:
921 constraints_using_intervals.push_back(c);
923 case ConstraintProto::ConstraintCase::kNoOverlap:
924 constraints_using_intervals.push_back(c);
926 case ConstraintProto::ConstraintCase::kNoOverlap2D:
927 constraints_using_intervals.push_back(c);
929 case ConstraintProto::ConstraintCase::kReservoir:
932 case ConstraintProto::ConstraintCase::kDummyConstraint:
933 return "The dummy constraint should never appear in a model.";
941 if (!support_enforcement && !
ct.enforcement_literal().empty()) {
942 for (
const int ref :
ct.enforcement_literal()) {
945 if (domain.
Size() != 1) {
947 "Enforcement literal not supported in constraint: ",
955 for (
const int c : constraints_using_intervals) {
957 ValidateIntervalsUsedInConstraint(after_presolve,
model, c));
960 switch (
ct.constraint_case()) {
961 case ConstraintProto::ConstraintCase::kCumulative:
964 case ConstraintProto::ConstraintCase::kNoOverlap:
966 case ConstraintProto::ConstraintCase::kNoOverlap2D:
970 LOG(DFATAL) <<
"Shouldn't be here";
974 if (
model.has_objective() &&
model.has_floating_point_objective()) {
975 return "A model cannot have both an objective and a floating point "
978 if (
model.has_objective()) {
981 if (
model.has_floating_point_objective()) {
987 for (
const int ref :
model.assumptions()) {
988 if (!LiteralReferenceIsValid(
model, ref)) {
989 return absl::StrCat(
"Invalid literal reference ", ref,
990 " in the 'assumptions' field.");
996#undef RETURN_IF_NOT_EMPTY
1004class ConstraintChecker {
1006 explicit ConstraintChecker(
const std::vector<int64_t>& variable_values)
1007 : variable_values_(variable_values) {}
1009 bool LiteralIsTrue(
int l)
const {
1010 if (l >= 0)
return variable_values_[l] != 0;
1011 return variable_values_[-l - 1] == 0;
1014 bool LiteralIsFalse(
int l)
const {
return !LiteralIsTrue(l); }
1017 if (
var >= 0)
return variable_values_[
var];
1018 return -variable_values_[-
var - 1];
1021 bool ConstraintIsEnforced(
const ConstraintProto&
ct) {
1022 for (
const int lit :
ct.enforcement_literal()) {
1023 if (LiteralIsFalse(lit))
return false;
1028 bool BoolOrConstraintIsFeasible(
const ConstraintProto&
ct) {
1029 for (
const int lit :
ct.bool_or().literals()) {
1030 if (LiteralIsTrue(lit))
return true;
1035 bool BoolAndConstraintIsFeasible(
const ConstraintProto&
ct) {
1036 for (
const int lit :
ct.bool_and().literals()) {
1037 if (LiteralIsFalse(lit))
return false;
1042 bool AtMostOneConstraintIsFeasible(
const ConstraintProto&
ct) {
1043 int num_true_literals = 0;
1044 for (
const int lit :
ct.at_most_one().literals()) {
1045 if (LiteralIsTrue(lit)) ++num_true_literals;
1047 return num_true_literals <= 1;
1050 bool ExactlyOneConstraintIsFeasible(
const ConstraintProto&
ct) {
1051 int num_true_literals = 0;
1052 for (
const int lit :
ct.exactly_one().literals()) {
1053 if (LiteralIsTrue(lit)) ++num_true_literals;
1055 return num_true_literals == 1;
1058 bool BoolXorConstraintIsFeasible(
const ConstraintProto&
ct) {
1060 for (
const int lit :
ct.bool_xor().literals()) {
1061 sum ^= LiteralIsTrue(lit) ? 1 : 0;
1066 bool LinearConstraintIsFeasible(
const ConstraintProto&
ct) {
1068 const int num_variables =
ct.linear().coeffs_size();
1069 for (
int i = 0; i < num_variables; ++i) {
1070 sum +=
Value(
ct.linear().vars(i)) *
ct.linear().coeffs(i);
1075 int64_t LinearExpressionValue(
const LinearExpressionProto& expr)
const {
1076 int64_t sum = expr.offset();
1077 const int num_variables = expr.vars_size();
1078 for (
int i = 0; i < num_variables; ++i) {
1079 sum +=
Value(expr.vars(i)) * expr.coeffs(i);
1084 bool LinMaxConstraintIsFeasible(
const ConstraintProto&
ct) {
1085 const int64_t
max = LinearExpressionValue(
ct.lin_max().target());
1087 for (
int i = 0; i <
ct.lin_max().exprs_size(); ++i) {
1088 const int64_t expr_value = LinearExpressionValue(
ct.lin_max().exprs(i));
1089 actual_max =
std::max(actual_max, expr_value);
1091 return max == actual_max;
1094 bool IntProdConstraintIsFeasible(
const ConstraintProto&
ct) {
1095 const int64_t prod = LinearExpressionValue(
ct.int_prod().target());
1096 int64_t actual_prod = 1;
1097 for (
const LinearExpressionProto& expr :
ct.int_prod().exprs()) {
1098 actual_prod =
CapProd(actual_prod, LinearExpressionValue(expr));
1100 return prod == actual_prod;
1103 bool IntDivConstraintIsFeasible(
const ConstraintProto&
ct) {
1104 return LinearExpressionValue(
ct.int_div().target()) ==
1105 LinearExpressionValue(
ct.int_div().exprs(0)) /
1106 LinearExpressionValue(
ct.int_div().exprs(1));
1109 bool IntModConstraintIsFeasible(
const ConstraintProto&
ct) {
1110 return LinearExpressionValue(
ct.int_mod().target()) ==
1111 LinearExpressionValue(
ct.int_mod().exprs(0)) %
1112 LinearExpressionValue(
ct.int_mod().exprs(1));
1115 bool AllDiffConstraintIsFeasible(
const ConstraintProto&
ct) {
1116 absl::flat_hash_set<int64_t> values;
1117 for (
const LinearExpressionProto& expr :
ct.all_diff().exprs()) {
1118 const int64_t
value = LinearExpressionValue(expr);
1119 const auto [it, inserted] = values.insert(
value);
1120 if (!inserted)
return false;
1125 int64_t IntervalStart(
const IntervalConstraintProto&
interval)
const {
1126 return LinearExpressionValue(
interval.start());
1129 int64_t IntervalSize(
const IntervalConstraintProto&
interval)
const {
1130 return LinearExpressionValue(
interval.size());
1133 int64_t IntervalEnd(
const IntervalConstraintProto&
interval)
const {
1134 return LinearExpressionValue(
interval.end());
1137 bool IntervalConstraintIsFeasible(
const ConstraintProto&
ct) {
1138 const int64_t size = IntervalSize(
ct.interval());
1139 if (size < 0)
return false;
1140 return IntervalStart(
ct.interval()) + size == IntervalEnd(
ct.interval());
1143 bool NoOverlapConstraintIsFeasible(
const CpModelProto&
model,
1144 const ConstraintProto&
ct) {
1145 std::vector<std::pair<int64_t, int64_t>> start_durations_pairs;
1146 for (
const int i :
ct.no_overlap().intervals()) {
1147 const ConstraintProto& interval_constraint =
model.constraints(i);
1148 if (ConstraintIsEnforced(interval_constraint)) {
1149 const IntervalConstraintProto&
interval =
1150 interval_constraint.interval();
1151 start_durations_pairs.push_back(
1155 std::sort(start_durations_pairs.begin(), start_durations_pairs.end());
1157 for (
const auto pair : start_durations_pairs) {
1158 if (pair.first < previous_end)
return false;
1159 previous_end = pair.first + pair.second;
1164 bool IntervalsAreDisjoint(
const IntervalConstraintProto& interval1,
1165 const IntervalConstraintProto& interval2) {
1166 return IntervalEnd(interval1) <= IntervalStart(interval2) ||
1167 IntervalEnd(interval2) <= IntervalStart(interval1);
1170 bool IntervalIsEmpty(
const IntervalConstraintProto&
interval) {
1174 bool NoOverlap2DConstraintIsFeasible(
const CpModelProto&
model,
1175 const ConstraintProto&
ct) {
1176 const auto& arg =
ct.no_overlap_2d();
1179 std::vector<std::pair<
const IntervalConstraintProto*
const,
1180 const IntervalConstraintProto*
const>>
1181 enforced_intervals_xy;
1183 const int num_intervals = arg.x_intervals_size();
1184 CHECK_EQ(arg.y_intervals_size(), num_intervals);
1185 for (
int i = 0; i < num_intervals; ++i) {
1186 const ConstraintProto& x =
model.constraints(arg.x_intervals(i));
1187 const ConstraintProto& y =
model.constraints(arg.y_intervals(i));
1188 if (ConstraintIsEnforced(x) && ConstraintIsEnforced(y) &&
1189 (!arg.boxes_with_null_area_can_overlap() ||
1190 (!IntervalIsEmpty(x.interval()) &&
1191 !IntervalIsEmpty(y.interval())))) {
1192 enforced_intervals_xy.push_back({&x.interval(), &y.interval()});
1196 const int num_enforced_intervals = enforced_intervals_xy.size();
1197 for (
int i = 0; i < num_enforced_intervals; ++i) {
1198 for (
int j = i + 1; j < num_enforced_intervals; ++j) {
1199 const auto& xi = *enforced_intervals_xy[i].first;
1200 const auto& yi = *enforced_intervals_xy[i].second;
1201 const auto& xj = *enforced_intervals_xy[j].first;
1202 const auto& yj = *enforced_intervals_xy[j].second;
1203 if (!IntervalsAreDisjoint(xi, xj) && !IntervalsAreDisjoint(yi, yj) &&
1204 !IntervalIsEmpty(xi) && !IntervalIsEmpty(xj) &&
1205 !IntervalIsEmpty(yi) && !IntervalIsEmpty(yj)) {
1206 VLOG(1) <<
"Interval " << i <<
"(x=[" << IntervalStart(xi) <<
", "
1207 << IntervalEnd(xi) <<
"], y=[" << IntervalStart(yi) <<
", "
1208 << IntervalEnd(yi) <<
"]) and " << j <<
"(x=["
1209 << IntervalStart(xj) <<
", " << IntervalEnd(xj) <<
"], y=["
1210 << IntervalStart(yj) <<
", " << IntervalEnd(yj)
1211 <<
"]) are not disjoint.";
1219 bool CumulativeConstraintIsFeasible(
const CpModelProto&
model,
1220 const ConstraintProto&
ct) {
1222 const int64_t
capacity = LinearExpressionValue(
ct.cumulative().capacity());
1223 const int num_intervals =
ct.cumulative().intervals_size();
1224 absl::flat_hash_map<int64_t, int64_t> usage;
1225 for (
int i = 0; i < num_intervals; ++i) {
1226 const ConstraintProto& interval_constraint =
1227 model.constraints(
ct.cumulative().intervals(i));
1228 if (ConstraintIsEnforced(interval_constraint)) {
1229 const IntervalConstraintProto&
interval =
1230 interval_constraint.interval();
1232 const int64_t duration = IntervalSize(
interval);
1234 LinearExpressionValue(
ct.cumulative().demands(i));
1235 for (int64_t t =
start; t <
start + duration; ++t) {
1238 VLOG(1) <<
"time: " << t <<
" usage: " << usage[t]
1248 bool ElementConstraintIsFeasible(
const ConstraintProto&
ct) {
1249 if (
ct.element().vars().empty())
return false;
1251 if (index < 0 || index >=
ct.element().vars_size())
return false;
1255 bool TableConstraintIsFeasible(
const ConstraintProto&
ct) {
1256 const int size =
ct.table().vars_size();
1257 if (size == 0)
return true;
1258 for (
int row_start = 0; row_start <
ct.table().values_size();
1259 row_start += size) {
1261 while (
Value(
ct.table().vars(i)) ==
ct.table().values(row_start + i)) {
1263 if (i == size)
return !
ct.table().negated();
1266 return ct.table().negated();
1269 bool AutomatonConstraintIsFeasible(
const ConstraintProto&
ct) {
1271 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> transition_map;
1272 const int num_transitions =
ct.automaton().transition_tail().size();
1273 for (
int i = 0; i < num_transitions; ++i) {
1274 transition_map[{
ct.automaton().transition_tail(i),
1275 ct.automaton().transition_label(i)}] =
1276 ct.automaton().transition_head(i);
1280 int64_t current_state =
ct.automaton().starting_state();
1281 const int num_steps =
ct.automaton().vars_size();
1282 for (
int i = 0; i < num_steps; ++i) {
1283 const std::pair<int64_t, int64_t> key = {current_state,
1284 Value(
ct.automaton().vars(i))};
1285 if (!transition_map.contains(key)) {
1288 current_state = transition_map[key];
1292 for (
const int64_t
final :
ct.automaton().final_states()) {
1293 if (current_state ==
final)
return true;
1298 bool CircuitConstraintIsFeasible(
const ConstraintProto&
ct) {
1301 const int num_arcs =
ct.circuit().tails_size();
1302 absl::flat_hash_set<int>
nodes;
1303 absl::flat_hash_map<int, int> nexts;
1304 for (
int i = 0; i < num_arcs; ++i) {
1305 const int tail =
ct.circuit().tails(i);
1306 const int head =
ct.circuit().heads(i);
1309 if (LiteralIsFalse(
ct.circuit().literals(i)))
continue;
1310 if (nexts.contains(
tail)) {
1311 VLOG(1) <<
"Node with two outgoing arcs";
1320 for (
const int node :
nodes) {
1321 if (!nexts.contains(node)) {
1322 VLOG(1) <<
"Node with no next: " << node;
1325 if (nexts[node] == node)
continue;
1329 if (cycle_size == 0)
return true;
1333 absl::flat_hash_set<int> visited;
1334 int current = in_cycle;
1335 int num_visited = 0;
1336 while (!visited.contains(current)) {
1338 visited.insert(current);
1339 current = nexts[current];
1341 if (current != in_cycle) {
1342 VLOG(1) <<
"Rho shape";
1345 if (num_visited != cycle_size) {
1346 VLOG(1) <<
"More than one cycle";
1348 return num_visited == cycle_size;
1351 bool RoutesConstraintIsFeasible(
const ConstraintProto&
ct) {
1352 const int num_arcs =
ct.routes().tails_size();
1353 int num_used_arcs = 0;
1354 int num_self_arcs = 0;
1356 std::vector<int> tail_to_head;
1357 std::vector<int> depot_nexts;
1358 for (
int i = 0; i < num_arcs; ++i) {
1359 const int tail =
ct.routes().tails(i);
1360 const int head =
ct.routes().heads(i);
1363 tail_to_head.resize(num_nodes, -1);
1364 if (LiteralIsTrue(
ct.routes().literals(i))) {
1366 if (
tail == 0)
return false;
1372 depot_nexts.push_back(
head);
1374 if (tail_to_head[
tail] != -1)
return false;
1381 if (num_nodes == 0)
return true;
1385 for (
int start : depot_nexts) {
1387 while (
start != 0) {
1388 if (tail_to_head[
start] == -1)
return false;
1394 if (count != num_used_arcs) {
1395 VLOG(1) <<
"count: " << count <<
" != num_used_arcs:" << num_used_arcs;
1403 if (count - depot_nexts.size() + 1 + num_self_arcs != num_nodes) {
1404 VLOG(1) <<
"Not all nodes are covered!";
1411 bool InverseConstraintIsFeasible(
const ConstraintProto&
ct) {
1412 const int num_variables =
ct.inverse().f_direct_size();
1413 if (num_variables !=
ct.inverse().f_inverse_size())
return false;
1415 for (
int i = 0; i < num_variables; i++) {
1416 const int fi =
Value(
ct.inverse().f_direct(i));
1417 if (fi < 0 || num_variables <= fi)
return false;
1418 if (i !=
Value(
ct.inverse().f_inverse(fi)))
return false;
1423 bool ReservoirConstraintIsFeasible(
const ConstraintProto&
ct) {
1424 const int num_variables =
ct.reservoir().time_exprs_size();
1425 const int64_t min_level =
ct.reservoir().min_level();
1426 const int64_t max_level =
ct.reservoir().max_level();
1427 std::map<int64_t, int64_t> deltas;
1428 const bool has_active_variables =
ct.reservoir().active_literals_size() > 0;
1429 for (
int i = 0; i < num_variables; i++) {
1430 const int64_t
time = LinearExpressionValue(
ct.reservoir().time_exprs(i));
1431 if (!has_active_variables ||
1432 Value(
ct.reservoir().active_literals(i)) == 1) {
1433 deltas[
time] +=
ct.reservoir().level_changes(i);
1436 int64_t current_level = 0;
1437 for (
const auto&
delta : deltas) {
1438 current_level +=
delta.second;
1439 if (current_level < min_level || current_level > max_level) {
1440 VLOG(1) <<
"Reservoir level " << current_level
1441 <<
" is out of bounds at time" <<
delta.first;
1449 std::vector<int64_t> variable_values_;
1455 const std::vector<int64_t>& variable_values,
1457 const std::vector<int>* postsolve_mapping) {
1458 if (variable_values.size() !=
model.variables_size()) {
1459 VLOG(1) <<
"Wrong number of variables (" << variable_values.size()
1460 <<
") in the solution vector. It should be "
1461 <<
model.variables_size() <<
".";
1466 for (
int i = 0; i <
model.variables_size(); ++i) {
1468 VLOG(1) <<
"Variable #" << i <<
" has value " << variable_values[i]
1469 <<
" which do not fall in its domain: "
1476 ConstraintChecker checker(variable_values);
1478 for (
int c = 0; c <
model.constraints_size(); ++c) {
1481 if (!checker.ConstraintIsEnforced(
ct))
continue;
1483 bool is_feasible =
true;
1486 case ConstraintProto::ConstraintCase::kBoolOr:
1487 is_feasible = checker.BoolOrConstraintIsFeasible(
ct);
1489 case ConstraintProto::ConstraintCase::kBoolAnd:
1490 is_feasible = checker.BoolAndConstraintIsFeasible(
ct);
1492 case ConstraintProto::ConstraintCase::kAtMostOne:
1493 is_feasible = checker.AtMostOneConstraintIsFeasible(
ct);
1495 case ConstraintProto::ConstraintCase::kExactlyOne:
1496 is_feasible = checker.ExactlyOneConstraintIsFeasible(
ct);
1498 case ConstraintProto::ConstraintCase::kBoolXor:
1499 is_feasible = checker.BoolXorConstraintIsFeasible(
ct);
1501 case ConstraintProto::ConstraintCase::kLinear:
1502 is_feasible = checker.LinearConstraintIsFeasible(
ct);
1504 case ConstraintProto::ConstraintCase::kIntProd:
1505 is_feasible = checker.IntProdConstraintIsFeasible(
ct);
1507 case ConstraintProto::ConstraintCase::kIntDiv:
1508 is_feasible = checker.IntDivConstraintIsFeasible(
ct);
1510 case ConstraintProto::ConstraintCase::kIntMod:
1511 is_feasible = checker.IntModConstraintIsFeasible(
ct);
1513 case ConstraintProto::ConstraintCase::kLinMax:
1514 is_feasible = checker.LinMaxConstraintIsFeasible(
ct);
1516 case ConstraintProto::ConstraintCase::kAllDiff:
1517 is_feasible = checker.AllDiffConstraintIsFeasible(
ct);
1519 case ConstraintProto::ConstraintCase::kInterval:
1520 if (!checker.IntervalConstraintIsFeasible(
ct)) {
1521 if (
ct.interval().has_start()) {
1527 LOG(
ERROR) <<
"Warning, an interval constraint was likely used "
1528 "without a corresponding linear constraint linking "
1529 "its start, size and end.";
1531 is_feasible =
false;
1535 case ConstraintProto::ConstraintCase::kNoOverlap:
1536 is_feasible = checker.NoOverlapConstraintIsFeasible(
model,
ct);
1538 case ConstraintProto::ConstraintCase::kNoOverlap2D:
1539 is_feasible = checker.NoOverlap2DConstraintIsFeasible(
model,
ct);
1541 case ConstraintProto::ConstraintCase::kCumulative:
1542 is_feasible = checker.CumulativeConstraintIsFeasible(
model,
ct);
1544 case ConstraintProto::ConstraintCase::kElement:
1545 is_feasible = checker.ElementConstraintIsFeasible(
ct);
1547 case ConstraintProto::ConstraintCase::kTable:
1548 is_feasible = checker.TableConstraintIsFeasible(
ct);
1550 case ConstraintProto::ConstraintCase::kAutomaton:
1551 is_feasible = checker.AutomatonConstraintIsFeasible(
ct);
1553 case ConstraintProto::ConstraintCase::kCircuit:
1554 is_feasible = checker.CircuitConstraintIsFeasible(
ct);
1556 case ConstraintProto::ConstraintCase::kRoutes:
1557 is_feasible = checker.RoutesConstraintIsFeasible(
ct);
1559 case ConstraintProto::ConstraintCase::kInverse:
1560 is_feasible = checker.InverseConstraintIsFeasible(
ct);
1562 case ConstraintProto::ConstraintCase::kReservoir:
1563 is_feasible = checker.ReservoirConstraintIsFeasible(
ct);
1565 case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1574 VLOG(1) <<
"Failing constraint #" << c <<
" : "
1576 if (mapping_proto !=
nullptr && postsolve_mapping !=
nullptr) {
1577 std::vector<int> reverse_map(mapping_proto->
variables().size(), -1);
1578 for (
int var = 0;
var < postsolve_mapping->size(); ++
var) {
1579 reverse_map[(*postsolve_mapping)[
var]] =
var;
1582 VLOG(1) <<
"var: " <<
var <<
" mapped_to: " << reverse_map[
var]
1583 <<
" value: " << variable_values[
var] <<
" initial_domain: "
1585 <<
" postsolved_domain: "
1590 VLOG(1) <<
"var: " <<
var <<
" value: " << variable_values[
var];
1602 if (
model.has_objective()) {
1603 int64_t inner_objective = 0;
1604 const int num_variables =
model.objective().coeffs_size();
1605 for (
int i = 0; i < num_variables; ++i) {
1606 inner_objective += checker.Value(
model.objective().vars(i)) *
1607 model.objective().coeffs(i);
1609 if (!
model.objective().domain().empty()) {
1611 VLOG(1) <<
"Objective value not in domain!";
1615 double factor =
model.objective().scaling_factor();
1616 if (factor == 0.0) factor = 1.0;
1617 const double scaled_objective =
1619 (
static_cast<double>(inner_objective) +
model.objective().offset());
1620 VLOG(2) <<
"Checker inner objective = " << inner_objective;
1621 VLOG(2) <<
"Checker scaled objective = " << scaled_objective;
#define CHECK_EQ(val1, val2)
#define DCHECK_GE(val1, val2)
#define DCHECK_EQ(val1, val2)
#define VLOG(verboselevel)
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
static constexpr DomainReductionStrategy SELECT_MAX_VALUE
static constexpr VariableSelectionStrategy CHOOSE_MAX_DOMAIN_SIZE
static constexpr VariableSelectionStrategy CHOOSE_LOWEST_MIN
static constexpr DomainReductionStrategy SELECT_MEDIAN_VALUE
static constexpr DomainReductionStrategy SELECT_LOWER_HALF
static constexpr VariableSelectionStrategy CHOOSE_MIN_DOMAIN_SIZE
static constexpr VariableSelectionStrategy CHOOSE_HIGHEST_MAX
static constexpr VariableSelectionStrategy CHOOSE_FIRST
static constexpr DomainReductionStrategy SELECT_UPPER_HALF
#define RETURN_IF_NOT_EMPTY(statement)
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool RefIsPositive(int ref)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
bool SolutionIsFeasible(const CpModelProto &model, const std::vector< int64_t > &variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
int64_t CapProd(int64_t x, int64_t y)
std::string ProtobufDebugString(const P &message)
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
Returns true iff we have:
static int input(yyscan_t yyscanner)