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; \ 49 template <
typename ProtoWithDomain>
50 bool 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)});
60 bool VariableReferenceIsValid(
const CpModelProto&
model,
int reference) {
62 if (reference >=
model.variables_size())
return false;
63 return reference >= -static_cast<int>(
model.variables_size());
66 bool LiteralReferenceIsValid(
const CpModelProto&
model,
int reference) {
67 if (!VariableReferenceIsValid(
model, reference))
return false;
69 const int64_t min_domain = var_proto.domain(0);
70 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
71 return min_domain >= 0 && max_domain <= 1;
74 std::string ValidateIntegerVariable(
const CpModelProto&
model,
int v) {
75 const IntegerVariableProto&
proto =
model.variables(v);
76 if (
proto.domain_size() == 0) {
77 return absl::StrCat(
"var #", v,
80 if (
proto.domain_size() % 2 != 0) {
81 return absl::StrCat(
"var #", v,
" has an odd domain() size: ",
84 if (!DomainInProtoIsValid(
proto)) {
85 return absl::StrCat(
"var #", v,
" has and invalid domain() format: ",
92 const int64_t lb =
proto.domain(0);
93 const int64_t ub =
proto.domain(
proto.domain_size() - 1);
97 "var #", v,
" domain do not fall in [kint64min + 2, kint64max - 1]. ",
106 " has a domain that is too large, i.e. |UB - LB| overflow an int64_t: ",
113 std::string ValidateArgumentReferencesInConstraint(
const CpModelProto&
model,
115 const ConstraintProto&
ct =
model.constraints(c);
117 for (
const int v : references.variables) {
118 if (!VariableReferenceIsValid(
model, v)) {
119 return absl::StrCat(
"Out of bound integer variable ", v,
120 " in constraint #", c,
" : ",
124 for (
const int lit :
ct.enforcement_literal()) {
125 if (!LiteralReferenceIsValid(
model, lit)) {
126 return absl::StrCat(
"Invalid enforcement literal ", lit,
127 " in constraint #", c,
" : ",
131 for (
const int lit : references.literals) {
132 if (!LiteralReferenceIsValid(
model, lit)) {
133 return absl::StrCat(
"Invalid literal ", lit,
" in constraint #", c,
" : ",
138 if (i < 0 || i >=
model.constraints_size()) {
139 return absl::StrCat(
"Out of bound interval ", i,
" in constraint #", c,
142 if (
model.constraints(i).constraint_case() !=
143 ConstraintProto::ConstraintCase::kInterval) {
146 " does not refer to an interval constraint. Problematic constraint #",
153 template <
class LinearExpressionProto>
154 bool PossibleIntegerOverflow(
const CpModelProto&
model,
155 const LinearExpressionProto&
proto,
156 int64_t offset = 0) {
157 int64_t sum_min = -std::abs(offset);
158 int64_t sum_max = +std::abs(offset);
159 for (
int i = 0; i <
proto.vars_size(); ++i) {
160 const int ref =
proto.vars(i);
162 const int64_t min_domain = var_proto.domain(0);
163 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
165 const int64_t coeff =
167 const int64_t prod1 =
CapProd(min_domain, coeff);
168 const int64_t prod2 =
CapProd(max_domain, coeff);
175 for (
const int64_t v : {prod1, prod2, sum_min, sum_max}) {
190 int64_t MinOfRef(
const CpModelProto&
model,
int ref) {
193 return var_proto.domain(0);
195 return -var_proto.domain(var_proto.domain_size() - 1);
199 int64_t MaxOfRef(
const CpModelProto&
model,
int ref) {
202 return var_proto.domain(var_proto.domain_size() - 1);
204 return -var_proto.domain(0);
208 template <
class LinearExpressionProto>
209 int64_t MinOfExpression(
const CpModelProto&
model,
210 const LinearExpressionProto&
proto) {
211 int64_t sum_min =
proto.offset();
212 for (
int i = 0; i <
proto.vars_size(); ++i) {
213 const int ref =
proto.vars(i);
214 const int64_t coeff =
proto.coeffs(i);
223 template <
class LinearExpressionProto>
224 int64_t MaxOfExpression(
const CpModelProto&
model,
225 const LinearExpressionProto&
proto) {
226 int64_t sum_max =
proto.offset();
227 for (
int i = 0; i <
proto.vars_size(); ++i) {
228 const int ref =
proto.vars(i);
229 const int64_t coeff =
proto.coeffs(i);
238 int64_t IntervalSizeMin(
const CpModelProto&
model,
int interval_index) {
239 DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
240 model.constraints(interval_index).constraint_case());
241 const IntervalConstraintProto&
proto =
242 model.constraints(interval_index).interval();
243 if (
proto.has_size_view()) {
244 return MinOfExpression(
model,
proto.size_view());
250 int64_t IntervalSizeMax(
const CpModelProto&
model,
int interval_index) {
251 DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
252 model.constraints(interval_index).constraint_case());
253 const IntervalConstraintProto&
proto =
254 model.constraints(interval_index).interval();
255 if (
proto.has_size_view()) {
256 return MaxOfExpression(
model,
proto.size_view());
262 Domain DomainOfRef(
const CpModelProto&
model,
int ref) {
267 std::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: ",
273 if (PossibleIntegerOverflow(
model, expr, expr.offset())) {
274 return absl::StrCat(
"Possible overflow in linear expression: ",
280 std::string ValidateLinearConstraint(
const CpModelProto&
model,
281 const ConstraintProto&
ct) {
282 if (!DomainInProtoIsValid(
ct.linear())) {
283 return absl::StrCat(
"Invalid domain in constraint : ",
286 if (
ct.linear().coeffs_size() !=
ct.linear().vars_size()) {
287 return absl::StrCat(
"coeffs_size() != vars_size() in constraint: ",
290 const LinearConstraintProto& arg =
ct.linear();
291 if (PossibleIntegerOverflow(
model, arg)) {
292 return "Possible integer overflow in constraint: " +
298 std::string ValidateIntModConstraint(
const CpModelProto&
model,
299 const ConstraintProto&
ct) {
300 if (
ct.int_mod().vars().size() != 2) {
301 return absl::StrCat(
"An int_mod constraint should have exactly 2 terms: ",
304 const int mod_var =
ct.int_mod().vars(1);
305 const IntegerVariableProto& mod_proto =
model.variables(
PositiveRef(mod_var));
308 mod_proto.domain(mod_proto.domain_size() - 1) >= 0)) {
310 "An int_mod must have a strictly positive modulo argument: ",
316 std::string ValidateIntProdConstraint(
const CpModelProto&
model,
317 const ConstraintProto&
ct) {
318 if (
ct.int_prod().vars().size() != 2) {
319 return absl::StrCat(
"An int_prod constraint should have exactly 2 terms: ",
324 const Domain product_domain =
329 product_domain.Min() < 0) ||
331 product_domain.Max() > 0)) {
332 return absl::StrCat(
"Potential integer overflow in constraint: ",
338 std::string ValidateIntDivConstraint(
const CpModelProto&
model,
339 const ConstraintProto&
ct) {
340 if (
ct.int_div().vars().size() != 2) {
341 return absl::StrCat(
"An int_div constraint should have exactly 2 terms: ",
344 const IntegerVariableProto& divisor_proto =
346 if (divisor_proto.domain(0) <= 0 &&
347 divisor_proto.domain(divisor_proto.domain_size() - 1) >= 0) {
348 return absl::StrCat(
"The divisor cannot span across zero in constraint: ",
354 std::string ValidateTableConstraint(
const CpModelProto&
model,
355 const ConstraintProto&
ct) {
356 const TableConstraintProto& arg =
ct.table();
357 if (arg.vars().empty())
return "";
358 if (arg.values().size() % arg.vars().size() != 0) {
360 "The flat encoding of a table constraint must be a multiple of the " 361 "number of variable: ",
367 std::string ValidateAutomatonConstraint(
const CpModelProto&
model,
368 const ConstraintProto&
ct) {
369 const int num_transistions =
ct.automaton().transition_tail().size();
370 if (num_transistions !=
ct.automaton().transition_head().size() ||
371 num_transistions !=
ct.automaton().transition_label().size()) {
373 "The transitions repeated fields must have the same size: ",
376 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> tail_label_to_head;
377 for (
int i = 0; i < num_transistions; ++i) {
378 const int64_t
tail =
ct.automaton().transition_tail(i);
379 const int64_t
head =
ct.automaton().transition_head(i);
380 const int64_t label =
ct.automaton().transition_label(i);
381 const auto [it, inserted] =
382 tail_label_to_head.insert({{
tail, label},
head});
384 if (it->second ==
head) {
385 return absl::StrCat(
"automaton: duplicate transition ",
tail,
" --(",
386 label,
")--> ",
head);
388 return absl::StrCat(
"automaton: incompatible transitions ",
tail,
389 " --(", label,
")--> ",
head,
" and ",
tail,
" --(",
390 label,
")--> ", it->second);
397 template <
typename GraphProto>
398 std::string ValidateGraphInput(
bool is_route,
const CpModelProto&
model,
399 const GraphProto& graph) {
400 const int size = graph.tails().size();
401 if (graph.heads().size() != size || graph.literals().size() != size) {
402 return absl::StrCat(
"Wrong field sizes in graph: ",
407 absl::flat_hash_set<int> self_loops;
408 for (
int i = 0; i < size; ++i) {
409 if (graph.heads(i) != graph.tails(i))
continue;
410 if (!self_loops.insert(graph.heads(i)).second) {
412 "Circuit/Route constraint contains multiple self-loop involving " 416 if (is_route && graph.tails(i) == 0) {
418 "A route constraint cannot have a self-loop on the depot (node 0)");
425 std::string ValidateRoutesConstraint(
const CpModelProto&
model,
426 const ConstraintProto&
ct) {
428 absl::flat_hash_set<int>
nodes;
429 for (
const int node :
ct.routes().tails()) {
431 return "All node in a route constraint must be in [0, num_nodes)";
434 max_node =
std::max(max_node, node);
436 for (
const int node :
ct.routes().heads()) {
438 return "All node in a route constraint must be in [0, num_nodes)";
441 max_node =
std::max(max_node, node);
443 if (!
nodes.empty() && max_node !=
nodes.size() - 1) {
445 "All nodes in a route constraint must have incident arcs");
448 return ValidateGraphInput(
true,
model,
ct.routes());
451 std::string ValidateDomainIsPositive(
const CpModelProto&
model,
int ref,
452 const std::string& ref_name) {
454 const IntegerVariableProto& var_proto =
model.variables(
NegatedRef(ref));
455 if (var_proto.domain(var_proto.domain_size() - 1) > 0) {
456 return absl::StrCat(
"Negative value in ", ref_name,
457 " domain: negation of ",
461 const IntegerVariableProto& var_proto =
model.variables(ref);
462 if (var_proto.domain(0) < 0) {
463 return absl::StrCat(
"Negative value in ", ref_name,
470 void AppendToOverflowValidator(
const LinearExpressionProto&
input,
471 LinearExpressionProto* output) {
472 output->mutable_vars()->Add(
input.vars().begin(),
input.vars().end());
473 output->mutable_coeffs()->Add(
input.coeffs().begin(),
input.coeffs().end());
478 CapAdd(std::abs(output->offset()), std::abs(
input.offset())));
481 std::string ValidateIntervalConstraint(
const CpModelProto&
model,
482 const ConstraintProto&
ct) {
483 if (
ct.enforcement_literal().size() > 1) {
485 "Interval with more than one enforcement literals are currently not " 490 LinearExpressionProto for_overflow_validation;
492 const IntervalConstraintProto& arg =
ct.interval();
494 if (arg.has_start_view()) {
496 if (arg.start_view().vars_size() > 1) {
497 return "Interval with a start expression containing more than one " 498 "variable are currently not supported.";
501 AppendToOverflowValidator(arg.start_view(), &for_overflow_validation);
503 for_overflow_validation.add_vars(arg.start());
504 for_overflow_validation.add_coeffs(1);
506 if (arg.has_size_view()) {
508 if (arg.size_view().vars_size() > 1) {
509 return "Interval with a size expression containing more than one " 510 "variable are currently not supported.";
513 if (
ct.enforcement_literal().empty() &&
514 MinOfExpression(
model, arg.size_view()) < 0) {
516 "The size of an performed interval must be >= 0 in constraint: ",
519 AppendToOverflowValidator(arg.size_view(), &for_overflow_validation);
521 if (
ct.enforcement_literal().empty()) {
522 const std::string domain_error =
523 ValidateDomainIsPositive(
model, arg.size(),
"size");
524 if (!domain_error.empty()) {
525 return absl::StrCat(domain_error,
530 for_overflow_validation.add_vars(arg.size());
531 for_overflow_validation.add_coeffs(1);
533 if (arg.has_end_view()) {
535 if (arg.end_view().vars_size() > 1) {
536 return "Interval with a end expression containing more than one " 537 "variable are currently not supported.";
540 AppendToOverflowValidator(arg.end_view(), &for_overflow_validation);
542 for_overflow_validation.add_vars(arg.end());
543 for_overflow_validation.add_coeffs(1);
546 if (PossibleIntegerOverflow(
model, for_overflow_validation,
547 for_overflow_validation.offset())) {
548 return absl::StrCat(
"Possible overflow in interval: ",
552 if (num_view != 0 && num_view != 3) {
554 "Interval must use either the var or the view representation, but not " 561 std::string ValidateCumulativeConstraint(
const CpModelProto&
model,
562 const ConstraintProto&
ct) {
563 if (
ct.cumulative().intervals_size() !=
ct.cumulative().demands_size()) {
564 return absl::StrCat(
"intervals_size() != demands_size() in constraint: ",
568 if (
ct.cumulative().energies_size() != 0 &&
569 ct.cumulative().energies_size() !=
ct.cumulative().intervals_size()) {
570 return absl::StrCat(
"energies_size() != intervals_size() in constraint: ",
574 for (
const int demand_ref :
ct.cumulative().demands()) {
575 const std::string domain_error =
576 ValidateDomainIsPositive(
model, demand_ref,
"demand");
577 if (!domain_error.empty()) {
578 return absl::StrCat(domain_error,
583 int64_t sum_max_demands = 0;
584 for (
const int demand_ref :
ct.cumulative().demands()) {
585 const int64_t demand_max = MaxOfRef(
model, demand_ref);
587 sum_max_demands =
CapAdd(sum_max_demands, demand_max);
589 return "The sum of max demands do not fit on an int64_t in constraint: " +
594 int64_t sum_max_energies = 0;
595 for (
int i = 0; i <
ct.cumulative().intervals_size(); ++i) {
596 const int64_t demand_max = MaxOfRef(
model,
ct.cumulative().demands(i));
597 const int64_t size_max =
598 IntervalSizeMax(
model,
ct.cumulative().intervals(i));
599 sum_max_energies =
CapAdd(sum_max_energies,
CapProd(size_max, demand_max));
601 return "The sum of max energies (size * demand) do not fit on an int64_t " 607 for (
int i = 0; i <
ct.cumulative().energies_size(); ++i) {
608 const LinearExpressionProto& expr =
ct.cumulative().energies(i);
609 const std::string error = ValidateLinearExpression(
model, expr);
610 if (!error.empty()) {
611 return absl::StrCat(error,
"in energy expression of constraint: ",
618 const int interval =
ct.cumulative().intervals(i);
621 const Domain product =
622 DomainOfRef(
model,
ct.cumulative().demands(i))
623 .ContinuousMultiplicationBy({size_min, size_max});
624 const int64_t energy_min = MinOfExpression(
model, expr);
625 const int64_t energy_max = MaxOfExpression(
model, expr);
626 if (product.IntersectionWith({energy_min, energy_max}).IsEmpty()) {
628 "The energy expression (with index ", i,
629 ") is not compatible with the product of the size of the interval, " 630 "and the demand in constraint: ",
638 std::string ValidateNoOverlap2DConstraint(
const CpModelProto&
model,
639 const ConstraintProto&
ct) {
640 const int size_x =
ct.no_overlap_2d().x_intervals().size();
641 const int size_y =
ct.no_overlap_2d().y_intervals().size();
642 if (size_x != size_y) {
643 return absl::StrCat(
"The two lists of intervals must have the same size: ",
648 int64_t sum_max_areas = 0;
649 for (
int i = 0; i <
ct.no_overlap_2d().x_intervals().size(); ++i) {
650 const int64_t max_size_x =
651 IntervalSizeMax(
model,
ct.no_overlap_2d().x_intervals(i));
652 const int64_t max_size_y =
653 IntervalSizeMax(
model,
ct.no_overlap_2d().y_intervals(i));
654 sum_max_areas =
CapAdd(sum_max_areas,
CapProd(max_size_x, max_size_y));
656 return "Integer overflow when summing all areas in " 664 std::string ValidateReservoirConstraint(
const CpModelProto&
model,
665 const ConstraintProto&
ct) {
666 if (
ct.enforcement_literal_size() > 0) {
667 return "Reservoir does not support enforcement literals.";
669 if (
ct.reservoir().times().size() !=
ct.reservoir().demands().size()) {
670 return absl::StrCat(
"Times and demands fields must be of the same size: ",
673 if (
ct.reservoir().min_level() > 0) {
675 "The min level of a reservoir must be <= 0. Please use fixed events to " 676 "setup initial state: ",
679 if (
ct.reservoir().max_level() < 0) {
681 "The max level of a reservoir must be >= 0. Please use fixed events to " 682 "setup initial state: ",
687 for (
const int64_t
demand :
ct.reservoir().demands()) {
690 return "Possible integer overflow in constraint: " +
695 return "Possible integer overflow in constraint: " +
699 if (
ct.reservoir().actives_size() > 0 &&
700 ct.reservoir().actives_size() !=
ct.reservoir().times_size()) {
701 return "Wrong array length of actives variables";
703 if (
ct.reservoir().demands_size() > 0 &&
704 ct.reservoir().demands_size() !=
ct.reservoir().times_size()) {
705 return "Wrong array length of demands variables";
710 std::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: " +
733 std::string ValidateSearchStrategies(
const CpModelProto&
model) {
734 for (
const DecisionStrategyProto& strategy :
model.search_strategy()) {
735 const int vss = strategy.variable_selection_strategy();
742 "Unknown or unsupported variable_selection_strategy: ", vss);
744 const int drs = strategy.domain_reduction_strategy();
750 return absl::StrCat(
"Unknown or unsupported domain_reduction_strategy: ",
753 for (
const int ref : strategy.variables()) {
754 if (!VariableReferenceIsValid(
model, ref)) {
755 return absl::StrCat(
"Invalid variable reference in strategy: ",
761 return absl::StrCat(
"Variable #",
PositiveRef(ref),
762 " has a domain too large to be used in a" 763 " SELECT_MEDIAN_VALUE value selection strategy");
766 int previous_index = -1;
767 for (
const auto& transformation : strategy.transformations()) {
768 if (transformation.positive_coeff() <= 0) {
769 return absl::StrCat(
"Affine transformation coeff should be positive: ",
772 if (transformation.index() <= previous_index ||
773 transformation.index() >= strategy.variables_size()) {
775 "Invalid indices (must be sorted and valid) in transformation: ",
778 previous_index = transformation.index();
784 std::string ValidateSolutionHint(
const CpModelProto&
model) {
785 if (!
model.has_solution_hint())
return "";
786 const auto& hint =
model.solution_hint();
787 if (hint.vars().size() != hint.values().size()) {
788 return "Invalid solution hint: vars and values do not have the same size.";
790 for (
const int ref : hint.vars()) {
791 if (!VariableReferenceIsValid(
model, ref)) {
792 return absl::StrCat(
"Invalid variable reference in solution hint: ", ref);
797 absl::flat_hash_set<int> indices;
798 for (
const int var : hint.vars()) {
800 if (!insert.second) {
802 "The solution hint contains duplicate variables like the variable " 814 for (
int v = 0; v <
model.variables_size(); ++v) {
820 for (
int c = 0; c <
model.constraints_size(); ++c) {
829 for (
int c = 0; c <
model.constraints_size(); ++c) {
832 bool support_enforcement =
false;
839 case ConstraintProto::ConstraintCase::kBoolOr:
840 support_enforcement =
true;
842 case ConstraintProto::ConstraintCase::kBoolAnd:
843 support_enforcement =
true;
845 case ConstraintProto::ConstraintCase::kLinear:
846 support_enforcement =
true;
849 case ConstraintProto::ConstraintCase::kLinMax: {
851 ValidateLinearExpression(
model,
ct.lin_max().target()));
852 for (
int i = 0; i <
ct.lin_max().exprs_size(); ++i) {
854 ValidateLinearExpression(
model,
ct.lin_max().exprs(i)));
858 case ConstraintProto::ConstraintCase::kLinMin: {
860 ValidateLinearExpression(
model,
ct.lin_min().target()));
861 for (
int i = 0; i <
ct.lin_min().exprs_size(); ++i) {
863 ValidateLinearExpression(
model,
ct.lin_min().exprs(i)));
867 case ConstraintProto::ConstraintCase::kIntProd:
870 case ConstraintProto::ConstraintCase::kIntDiv:
873 case ConstraintProto::ConstraintCase::kIntMod:
876 case ConstraintProto::ConstraintCase::kInverse:
877 if (
ct.inverse().f_direct().size() !=
ct.inverse().f_inverse().size()) {
878 return absl::StrCat(
"Non-matching fields size in inverse: ",
882 case ConstraintProto::ConstraintCase::kTable:
885 case ConstraintProto::ConstraintCase::kAutomaton:
888 case ConstraintProto::ConstraintCase::kCircuit:
890 ValidateGraphInput(
false,
model,
ct.circuit()));
892 case ConstraintProto::ConstraintCase::kRoutes:
895 case ConstraintProto::ConstraintCase::kInterval:
896 support_enforcement =
true;
898 case ConstraintProto::ConstraintCase::kCumulative:
901 case ConstraintProto::ConstraintCase::kNoOverlap2D:
904 case ConstraintProto::ConstraintCase::kReservoir:
907 case ConstraintProto::ConstraintCase::kDummyConstraint:
908 return "The dummy constraint should never appear in a model.";
916 if (!support_enforcement && !
ct.enforcement_literal().empty()) {
917 for (
const int ref :
ct.enforcement_literal()) {
920 if (domain.
Size() != 1) {
922 "Enforcement literal not supported in constraint: ",
928 if (
model.has_objective()) {
933 for (
const int ref :
model.assumptions()) {
934 if (!LiteralReferenceIsValid(
model, ref)) {
935 return absl::StrCat(
"Invalid literal reference ", ref,
936 " in the 'assumptions' field.");
942 #undef RETURN_IF_NOT_EMPTY 950 class ConstraintChecker {
952 explicit ConstraintChecker(
const std::vector<int64_t>& variable_values)
953 : variable_values_(variable_values) {}
955 bool LiteralIsTrue(
int l)
const {
956 if (l >= 0)
return variable_values_[l] != 0;
957 return variable_values_[-l - 1] == 0;
960 bool LiteralIsFalse(
int l)
const {
return !LiteralIsTrue(l); }
963 if (
var >= 0)
return variable_values_[
var];
964 return -variable_values_[-
var - 1];
967 bool ConstraintIsEnforced(
const ConstraintProto&
ct) {
968 for (
const int lit :
ct.enforcement_literal()) {
969 if (LiteralIsFalse(lit))
return false;
974 bool BoolOrConstraintIsFeasible(
const ConstraintProto&
ct) {
975 for (
const int lit :
ct.bool_or().literals()) {
976 if (LiteralIsTrue(lit))
return true;
981 bool BoolAndConstraintIsFeasible(
const ConstraintProto&
ct) {
982 for (
const int lit :
ct.bool_and().literals()) {
983 if (LiteralIsFalse(lit))
return false;
988 bool AtMostOneConstraintIsFeasible(
const ConstraintProto&
ct) {
989 int num_true_literals = 0;
990 for (
const int lit :
ct.at_most_one().literals()) {
991 if (LiteralIsTrue(lit)) ++num_true_literals;
993 return num_true_literals <= 1;
996 bool ExactlyOneConstraintIsFeasible(
const ConstraintProto&
ct) {
997 int num_true_literals = 0;
998 for (
const int lit :
ct.exactly_one().literals()) {
999 if (LiteralIsTrue(lit)) ++num_true_literals;
1001 return num_true_literals == 1;
1004 bool BoolXorConstraintIsFeasible(
const ConstraintProto&
ct) {
1006 for (
const int lit :
ct.bool_xor().literals()) {
1007 sum ^= LiteralIsTrue(lit) ? 1 : 0;
1012 bool LinearConstraintIsFeasible(
const ConstraintProto&
ct) {
1014 const int num_variables =
ct.linear().coeffs_size();
1015 for (
int i = 0; i < num_variables; ++i) {
1016 sum +=
Value(
ct.linear().vars(i)) *
ct.linear().coeffs(i);
1021 bool IntMaxConstraintIsFeasible(
const ConstraintProto&
ct) {
1022 const int64_t
max =
Value(
ct.int_max().target());
1024 for (
int i = 0; i <
ct.int_max().vars_size(); ++i) {
1027 return max == actual_max;
1030 int64_t LinearExpressionValue(
const LinearExpressionProto& expr)
const {
1031 int64_t sum = expr.offset();
1032 const int num_variables = expr.vars_size();
1033 for (
int i = 0; i < num_variables; ++i) {
1034 sum +=
Value(expr.vars(i)) * expr.coeffs(i);
1039 bool LinMaxConstraintIsFeasible(
const ConstraintProto&
ct) {
1040 const int64_t
max = LinearExpressionValue(
ct.lin_max().target());
1042 for (
int i = 0; i <
ct.lin_max().exprs_size(); ++i) {
1043 const int64_t expr_value = LinearExpressionValue(
ct.lin_max().exprs(i));
1044 actual_max =
std::max(actual_max, expr_value);
1046 return max == actual_max;
1049 bool IntProdConstraintIsFeasible(
const ConstraintProto&
ct) {
1050 const int64_t prod =
Value(
ct.int_prod().target());
1051 int64_t actual_prod = 1;
1052 for (
int i = 0; i <
ct.int_prod().vars_size(); ++i) {
1053 actual_prod *=
Value(
ct.int_prod().vars(i));
1055 return prod == actual_prod;
1058 bool IntDivConstraintIsFeasible(
const ConstraintProto&
ct) {
1059 return Value(
ct.int_div().target()) ==
1063 bool IntModConstraintIsFeasible(
const ConstraintProto&
ct) {
1064 return Value(
ct.int_mod().target()) ==
1068 bool IntMinConstraintIsFeasible(
const ConstraintProto&
ct) {
1069 const int64_t
min =
Value(
ct.int_min().target());
1071 for (
int i = 0; i <
ct.int_min().vars_size(); ++i) {
1074 return min == actual_min;
1077 bool LinMinConstraintIsFeasible(
const ConstraintProto&
ct) {
1078 const int64_t
min = LinearExpressionValue(
ct.lin_min().target());
1080 for (
int i = 0; i <
ct.lin_min().exprs_size(); ++i) {
1081 const int64_t expr_value = LinearExpressionValue(
ct.lin_min().exprs(i));
1082 actual_min =
std::min(actual_min, expr_value);
1084 return min == actual_min;
1087 bool AllDiffConstraintIsFeasible(
const ConstraintProto&
ct) {
1088 absl::flat_hash_set<int64_t> values;
1089 for (
const int v :
ct.all_diff().vars()) {
1091 values.insert(
Value(v));
1096 int64_t IntervalStart(
const IntervalConstraintProto&
interval)
const {
1098 ? LinearExpressionValue(
interval.start_view())
1102 int64_t IntervalSize(
const IntervalConstraintProto&
interval)
const {
1104 ? LinearExpressionValue(
interval.size_view())
1108 int64_t IntervalEnd(
const IntervalConstraintProto&
interval)
const {
1109 return interval.has_end_view() ? LinearExpressionValue(
interval.end_view())
1113 bool IntervalConstraintIsFeasible(
const ConstraintProto&
ct) {
1114 const int64_t size = IntervalSize(
ct.interval());
1115 if (size < 0)
return false;
1116 return IntervalStart(
ct.interval()) + size == IntervalEnd(
ct.interval());
1119 bool NoOverlapConstraintIsFeasible(
const CpModelProto&
model,
1120 const ConstraintProto&
ct) {
1121 std::vector<std::pair<int64_t, int64_t>> start_durations_pairs;
1122 for (
const int i :
ct.no_overlap().intervals()) {
1123 const ConstraintProto& interval_constraint =
model.constraints(i);
1124 if (ConstraintIsEnforced(interval_constraint)) {
1125 const IntervalConstraintProto&
interval =
1126 interval_constraint.interval();
1127 start_durations_pairs.push_back(
1131 std::sort(start_durations_pairs.begin(), start_durations_pairs.end());
1133 for (
const auto pair : start_durations_pairs) {
1134 if (pair.first < previous_end)
return false;
1135 previous_end = pair.first + pair.second;
1140 bool IntervalsAreDisjoint(
const IntervalConstraintProto& interval1,
1141 const IntervalConstraintProto& interval2) {
1142 return IntervalEnd(interval1) <= IntervalStart(interval2) ||
1143 IntervalEnd(interval2) <= IntervalStart(interval1);
1146 bool IntervalIsEmpty(
const IntervalConstraintProto&
interval) {
1150 bool NoOverlap2DConstraintIsFeasible(
const CpModelProto&
model,
1151 const ConstraintProto&
ct) {
1152 const auto& arg =
ct.no_overlap_2d();
1155 std::vector<std::pair<
const IntervalConstraintProto*
const,
1156 const IntervalConstraintProto*
const>>
1157 enforced_intervals_xy;
1159 const int num_intervals = arg.x_intervals_size();
1160 CHECK_EQ(arg.y_intervals_size(), num_intervals);
1161 for (
int i = 0; i < num_intervals; ++i) {
1162 const ConstraintProto& x =
model.constraints(arg.x_intervals(i));
1163 const ConstraintProto& y =
model.constraints(arg.y_intervals(i));
1164 if (ConstraintIsEnforced(x) && ConstraintIsEnforced(y) &&
1165 (!arg.boxes_with_null_area_can_overlap() ||
1166 (!IntervalIsEmpty(x.interval()) &&
1167 !IntervalIsEmpty(y.interval())))) {
1168 enforced_intervals_xy.push_back({&x.interval(), &y.interval()});
1172 const int num_enforced_intervals = enforced_intervals_xy.size();
1173 for (
int i = 0; i < num_enforced_intervals; ++i) {
1174 for (
int j = i + 1; j < num_enforced_intervals; ++j) {
1175 const auto& xi = *enforced_intervals_xy[i].first;
1176 const auto& yi = *enforced_intervals_xy[i].second;
1177 const auto& xj = *enforced_intervals_xy[j].first;
1178 const auto& yj = *enforced_intervals_xy[j].second;
1179 if (!IntervalsAreDisjoint(xi, xj) && !IntervalsAreDisjoint(yi, yj) &&
1180 !IntervalIsEmpty(xi) && !IntervalIsEmpty(xj) &&
1181 !IntervalIsEmpty(yi) && !IntervalIsEmpty(yj)) {
1182 VLOG(1) <<
"Interval " << i <<
"(x=[" << IntervalStart(xi) <<
", " 1183 << IntervalEnd(xi) <<
"], y=[" << IntervalStart(yi) <<
", " 1184 << IntervalEnd(yi) <<
"]) and " << j <<
"(x=[" 1185 << IntervalStart(xj) <<
", " << IntervalEnd(xj) <<
"], y=[" 1186 << IntervalStart(yj) <<
", " << IntervalEnd(yj)
1187 <<
"]) are not disjoint.";
1195 bool CumulativeConstraintIsFeasible(
const CpModelProto&
model,
1196 const ConstraintProto&
ct) {
1199 const int num_intervals =
ct.cumulative().intervals_size();
1200 absl::flat_hash_map<int64_t, int64_t> usage;
1201 for (
int i = 0; i < num_intervals; ++i) {
1202 const ConstraintProto& interval_constraint =
1203 model.constraints(
ct.cumulative().intervals(i));
1204 if (ConstraintIsEnforced(interval_constraint)) {
1205 const IntervalConstraintProto&
interval =
1206 interval_constraint.interval();
1207 const int64_t start = IntervalStart(
interval);
1208 const int64_t duration = IntervalSize(
interval);
1210 for (int64_t t = start; t < start + duration; ++t) {
1212 if (usage[t] >
capacity)
return false;
1214 if (!
ct.cumulative().energies().empty()) {
1215 const LinearExpressionProto& energy_expr =
1216 ct.cumulative().energies(i);
1217 int64_t
energy = energy_expr.offset();
1218 for (
int j = 0; j < energy_expr.vars_size(); ++j) {
1219 energy +=
Value(energy_expr.vars(j)) * energy_expr.coeffs(j);
1230 bool ElementConstraintIsFeasible(
const ConstraintProto&
ct) {
1231 if (
ct.element().vars().empty())
return false;
1233 if (index < 0 || index >=
ct.element().vars_size())
return false;
1237 bool TableConstraintIsFeasible(
const ConstraintProto&
ct) {
1238 const int size =
ct.table().vars_size();
1239 if (size == 0)
return true;
1240 for (
int row_start = 0; row_start <
ct.table().values_size();
1241 row_start += size) {
1243 while (
Value(
ct.table().vars(i)) ==
ct.table().values(row_start + i)) {
1245 if (i == size)
return !
ct.table().negated();
1248 return ct.table().negated();
1251 bool AutomatonConstraintIsFeasible(
const ConstraintProto&
ct) {
1253 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> transition_map;
1254 const int num_transitions =
ct.automaton().transition_tail().size();
1255 for (
int i = 0; i < num_transitions; ++i) {
1256 transition_map[{
ct.automaton().transition_tail(i),
1257 ct.automaton().transition_label(i)}] =
1258 ct.automaton().transition_head(i);
1262 int64_t current_state =
ct.automaton().starting_state();
1263 const int num_steps =
ct.automaton().vars_size();
1264 for (
int i = 0; i < num_steps; ++i) {
1265 const std::pair<int64_t, int64_t> key = {current_state,
1266 Value(
ct.automaton().vars(i))};
1270 current_state = transition_map[key];
1274 for (
const int64_t
final :
ct.automaton().final_states()) {
1275 if (current_state ==
final)
return true;
1280 bool CircuitConstraintIsFeasible(
const ConstraintProto&
ct) {
1283 const int num_arcs =
ct.circuit().tails_size();
1284 absl::flat_hash_set<int>
nodes;
1285 absl::flat_hash_map<int, int> nexts;
1286 for (
int i = 0; i < num_arcs; ++i) {
1287 const int tail =
ct.circuit().tails(i);
1288 const int head =
ct.circuit().heads(i);
1291 if (LiteralIsFalse(
ct.circuit().literals(i)))
continue;
1292 if (nexts.contains(
tail)) {
1293 VLOG(1) <<
"Node with two outgoing arcs";
1302 for (
const int node :
nodes) {
1303 if (!nexts.contains(node)) {
1304 VLOG(1) <<
"Node with no next: " << node;
1307 if (nexts[node] == node)
continue;
1311 if (cycle_size == 0)
return true;
1315 absl::flat_hash_set<int> visited;
1316 int current = in_cycle;
1317 int num_visited = 0;
1318 while (!visited.contains(current)) {
1320 visited.insert(current);
1321 current = nexts[current];
1323 if (current != in_cycle) {
1324 VLOG(1) <<
"Rho shape";
1327 if (num_visited != cycle_size) {
1328 VLOG(1) <<
"More than one cycle";
1330 return num_visited == cycle_size;
1333 bool RoutesConstraintIsFeasible(
const ConstraintProto&
ct) {
1334 const int num_arcs =
ct.routes().tails_size();
1335 int num_used_arcs = 0;
1336 int num_self_arcs = 0;
1338 std::vector<int> tail_to_head;
1339 std::vector<int> depot_nexts;
1340 for (
int i = 0; i < num_arcs; ++i) {
1341 const int tail =
ct.routes().tails(i);
1342 const int head =
ct.routes().heads(i);
1345 tail_to_head.resize(num_nodes, -1);
1346 if (LiteralIsTrue(
ct.routes().literals(i))) {
1348 if (
tail == 0)
return false;
1354 depot_nexts.push_back(
head);
1356 if (tail_to_head[
tail] != -1)
return false;
1363 if (num_nodes == 0)
return true;
1367 for (
int start : depot_nexts) {
1369 while (start != 0) {
1370 if (tail_to_head[start] == -1)
return false;
1371 start = tail_to_head[start];
1376 if (count != num_used_arcs) {
1377 VLOG(1) <<
"count: " << count <<
" != num_used_arcs:" << num_used_arcs;
1385 if (count - depot_nexts.size() + 1 + num_self_arcs != num_nodes) {
1386 VLOG(1) <<
"Not all nodes are covered!";
1393 bool InverseConstraintIsFeasible(
const ConstraintProto&
ct) {
1394 const int num_variables =
ct.inverse().f_direct_size();
1395 if (num_variables !=
ct.inverse().f_inverse_size())
return false;
1397 for (
int i = 0; i < num_variables; i++) {
1398 const int fi =
Value(
ct.inverse().f_direct(i));
1399 if (fi < 0 || num_variables <= fi)
return false;
1400 if (i !=
Value(
ct.inverse().f_inverse(fi)))
return false;
1405 bool ReservoirConstraintIsFeasible(
const ConstraintProto&
ct) {
1406 const int num_variables =
ct.reservoir().times_size();
1407 const int64_t min_level =
ct.reservoir().min_level();
1408 const int64_t max_level =
ct.reservoir().max_level();
1409 std::map<int64_t, int64_t> deltas;
1410 const bool has_active_variables =
ct.reservoir().actives_size() > 0;
1411 for (
int i = 0; i < num_variables; i++) {
1412 const int64_t
time =
Value(
ct.reservoir().times(i));
1413 if (!has_active_variables ||
Value(
ct.reservoir().actives(i)) == 1) {
1414 deltas[
time] +=
ct.reservoir().demands(i);
1417 int64_t current_level = 0;
1418 for (
const auto&
delta : deltas) {
1419 current_level +=
delta.second;
1420 if (current_level < min_level || current_level > max_level) {
1421 VLOG(1) <<
"Reservoir level " << current_level
1422 <<
" is out of bounds at time" <<
delta.first;
1430 std::vector<int64_t> variable_values_;
1436 const std::vector<int64_t>& variable_values,
1438 const std::vector<int>* postsolve_mapping) {
1439 if (variable_values.size() !=
model.variables_size()) {
1440 VLOG(1) <<
"Wrong number of variables in the solution vector";
1445 for (
int i = 0; i <
model.variables_size(); ++i) {
1447 VLOG(1) <<
"Variable #" << i <<
" has value " << variable_values[i]
1448 <<
" which do not fall in its domain: " 1455 ConstraintChecker checker(variable_values);
1457 for (
int c = 0; c <
model.constraints_size(); ++c) {
1460 if (!checker.ConstraintIsEnforced(
ct))
continue;
1462 bool is_feasible =
true;
1465 case ConstraintProto::ConstraintCase::kBoolOr:
1466 is_feasible = checker.BoolOrConstraintIsFeasible(
ct);
1468 case ConstraintProto::ConstraintCase::kBoolAnd:
1469 is_feasible = checker.BoolAndConstraintIsFeasible(
ct);
1471 case ConstraintProto::ConstraintCase::kAtMostOne:
1472 is_feasible = checker.AtMostOneConstraintIsFeasible(
ct);
1474 case ConstraintProto::ConstraintCase::kExactlyOne:
1475 is_feasible = checker.ExactlyOneConstraintIsFeasible(
ct);
1477 case ConstraintProto::ConstraintCase::kBoolXor:
1478 is_feasible = checker.BoolXorConstraintIsFeasible(
ct);
1480 case ConstraintProto::ConstraintCase::kLinear:
1481 is_feasible = checker.LinearConstraintIsFeasible(
ct);
1483 case ConstraintProto::ConstraintCase::kIntProd:
1484 is_feasible = checker.IntProdConstraintIsFeasible(
ct);
1486 case ConstraintProto::ConstraintCase::kIntDiv:
1487 is_feasible = checker.IntDivConstraintIsFeasible(
ct);
1489 case ConstraintProto::ConstraintCase::kIntMod:
1490 is_feasible = checker.IntModConstraintIsFeasible(
ct);
1492 case ConstraintProto::ConstraintCase::kIntMin:
1493 is_feasible = checker.IntMinConstraintIsFeasible(
ct);
1495 case ConstraintProto::ConstraintCase::kLinMin:
1496 is_feasible = checker.LinMinConstraintIsFeasible(
ct);
1498 case ConstraintProto::ConstraintCase::kIntMax:
1499 is_feasible = checker.IntMaxConstraintIsFeasible(
ct);
1501 case ConstraintProto::ConstraintCase::kLinMax:
1502 is_feasible = checker.LinMaxConstraintIsFeasible(
ct);
1504 case ConstraintProto::ConstraintCase::kAllDiff:
1505 is_feasible = checker.AllDiffConstraintIsFeasible(
ct);
1507 case ConstraintProto::ConstraintCase::kInterval:
1508 if (!checker.IntervalConstraintIsFeasible(
ct)) {
1509 if (
ct.interval().has_start_view()) {
1515 LOG(
ERROR) <<
"Warning, an interval constraint was likely used " 1516 "without a corresponding linear constraint linking " 1517 "its start, size and end.";
1519 is_feasible =
false;
1523 case ConstraintProto::ConstraintCase::kNoOverlap:
1524 is_feasible = checker.NoOverlapConstraintIsFeasible(
model,
ct);
1526 case ConstraintProto::ConstraintCase::kNoOverlap2D:
1527 is_feasible = checker.NoOverlap2DConstraintIsFeasible(
model,
ct);
1529 case ConstraintProto::ConstraintCase::kCumulative:
1530 is_feasible = checker.CumulativeConstraintIsFeasible(
model,
ct);
1532 case ConstraintProto::ConstraintCase::kElement:
1533 is_feasible = checker.ElementConstraintIsFeasible(
ct);
1535 case ConstraintProto::ConstraintCase::kTable:
1536 is_feasible = checker.TableConstraintIsFeasible(
ct);
1538 case ConstraintProto::ConstraintCase::kAutomaton:
1539 is_feasible = checker.AutomatonConstraintIsFeasible(
ct);
1541 case ConstraintProto::ConstraintCase::kCircuit:
1542 is_feasible = checker.CircuitConstraintIsFeasible(
ct);
1544 case ConstraintProto::ConstraintCase::kRoutes:
1545 is_feasible = checker.RoutesConstraintIsFeasible(
ct);
1547 case ConstraintProto::ConstraintCase::kInverse:
1548 is_feasible = checker.InverseConstraintIsFeasible(
ct);
1550 case ConstraintProto::ConstraintCase::kReservoir:
1551 is_feasible = checker.ReservoirConstraintIsFeasible(
ct);
1553 case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1562 VLOG(1) <<
"Failing constraint #" << c <<
" : " 1564 if (mapping_proto !=
nullptr && postsolve_mapping !=
nullptr) {
1565 std::vector<int> reverse_map(mapping_proto->
variables().size(), -1);
1566 for (
int var = 0;
var < postsolve_mapping->size(); ++
var) {
1567 reverse_map[(*postsolve_mapping)[
var]] =
var;
1570 VLOG(1) <<
"var: " <<
var <<
" mapped_to: " << reverse_map[
var]
1571 <<
" value: " << variable_values[
var] <<
" initial_domain: " 1573 <<
" postsolved_domain: " 1578 VLOG(1) <<
"var: " <<
var <<
" value: " << variable_values[
var];
1590 if (
model.has_objective()) {
1591 int64_t inner_objective = 0;
1592 const int num_variables =
model.objective().coeffs_size();
1593 for (
int i = 0; i < num_variables; ++i) {
1594 inner_objective += checker.Value(
model.objective().vars(i)) *
1595 model.objective().coeffs(i);
1597 if (!
model.objective().domain().empty()) {
1599 VLOG(1) <<
"Objective value not in domain!";
1603 double factor =
model.objective().scaling_factor();
1604 if (factor == 0.0) factor = 1.0;
1605 const double scaled_objective =
1607 (static_cast<double>(inner_objective) +
model.objective().offset());
1608 VLOG(2) <<
"Checker inner objective = " << inner_objective;
1609 VLOG(2) <<
"Checker scaled objective = " << scaled_objective;
std::vector< int > UsedVariables(const ConstraintProto &ct)
static constexpr DomainReductionStrategy SELECT_MIN_VALUE
static constexpr DomainReductionStrategy SELECT_MEDIAN_VALUE
static constexpr VariableSelectionStrategy CHOOSE_LOWEST_MIN
#define VLOG(verboselevel)
int64_t CapProd(int64_t x, int64_t y)
static constexpr VariableSelectionStrategy CHOOSE_HIGHEST_MAX
static constexpr VariableSelectionStrategy CHOOSE_FIRST
std::function< int64_t(const Model &)> Value(IntegerVariable v)
std::string ProtobufDebugString(const P &message)
std::string ValidateCpModel(const CpModelProto &model)
static constexpr VariableSelectionStrategy CHOOSE_MAX_DOMAIN_SIZE
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
int64_t CapAdd(int64_t x, int64_t y)
bool ContainsKey(const Collection &collection, const Key &key)
static int input(yyscan_t yyscanner)
int64_t Size() const
Returns the number of elements in the domain.
#define DCHECK_GE(val1, val2)
static constexpr DomainReductionStrategy SELECT_MAX_VALUE
#define CHECK_EQ(val1, val2)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
static constexpr DomainReductionStrategy SELECT_UPPER_HALF
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
We call domain any subset of Int64 = [kint64min, kint64max].
static constexpr VariableSelectionStrategy CHOOSE_MIN_DOMAIN_SIZE
#define DCHECK_EQ(val1, val2)
std::string ProtobufShortDebugString(const P &message)
#define RETURN_IF_NOT_EMPTY(statement)
Collection of objects used to extend the Constraint Solver library.
bool RefIsPositive(int ref)
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
Returns true iff we have:
static constexpr DomainReductionStrategy SELECT_LOWER_HALF
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
bool SolutionIsFeasible(const CpModelProto &model, const std::vector< int64_t > &variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)