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) {
158 for (
int i = 0; i <
proto.vars_size(); ++i) {
159 const int ref =
proto.vars(i);
161 const int64_t min_domain = var_proto.domain(0);
162 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
164 const int64_t coeff =
166 const int64_t prod1 =
CapProd(min_domain, coeff);
167 const int64_t prod2 =
CapProd(max_domain, coeff);
174 for (
const int64_t v : {prod1, prod2, sum_min, sum_max}) {
189 std::string ValidateLinearExpression(
const CpModelProto&
model,
190 const LinearExpressionProto& expr) {
191 if (expr.coeffs_size() != expr.vars_size()) {
192 return absl::StrCat(
"coeffs_size() != vars_size() in linear expression: ",
195 if (PossibleIntegerOverflow(
model, expr)) {
196 return absl::StrCat(
"Possible overflow in linear expression: ",
202 std::string ValidateIntervalConstraint(
const CpModelProto&
model,
203 const ConstraintProto&
ct) {
204 const IntervalConstraintProto& arg =
ct.interval();
206 if (arg.has_start_view()) {
210 if (arg.has_size_view()) {
214 if (arg.has_end_view()) {
218 if (num_view != 0 && num_view != 3) {
220 "Interval must use either the var or the view representation, but not "
224 if (num_view > 0)
return "";
225 if (arg.size() < 0) {
226 const IntegerVariableProto& size_var_proto =
228 if (size_var_proto.domain(size_var_proto.domain_size() - 1) > 0) {
234 const IntegerVariableProto& size_var_proto =
model.variables(arg.size());
235 if (size_var_proto.domain(0) < 0) {
244 std::string ValidateLinearConstraint(
const CpModelProto&
model,
245 const ConstraintProto&
ct) {
246 const LinearConstraintProto& arg =
ct.linear();
247 if (PossibleIntegerOverflow(
model, arg)) {
248 return "Possible integer overflow in constraint: " +
254 std::string ValidateTableConstraint(
const CpModelProto&
model,
255 const ConstraintProto&
ct) {
256 const TableConstraintProto& arg =
ct.table();
257 if (arg.vars().empty())
return "";
258 if (arg.values().size() % arg.vars().size() != 0) {
260 "The flat encoding of a table constraint must be a multiple of the "
261 "number of variable: ",
267 std::string ValidateCircuitConstraint(
const CpModelProto&
model,
268 const ConstraintProto&
ct) {
269 const int size =
ct.circuit().tails().size();
270 if (
ct.circuit().heads().size() != size ||
271 ct.circuit().literals().size() != size) {
272 return absl::StrCat(
"Wrong field sizes in circuit: ",
278 std::string ValidateRoutesConstraint(
const CpModelProto&
model,
279 const ConstraintProto&
ct) {
280 const int size =
ct.routes().tails().size();
281 if (
ct.routes().heads().size() != size ||
282 ct.routes().literals().size() != size) {
283 return absl::StrCat(
"Wrong field sizes in routes: ",
289 std::string ValidateNoOverlap2DConstraint(
const CpModelProto&
model,
290 const ConstraintProto&
ct) {
291 const int size_x =
ct.no_overlap_2d().x_intervals().size();
292 const int size_y =
ct.no_overlap_2d().y_intervals().size();
293 if (size_x != size_y) {
294 return absl::StrCat(
"The two lists of intervals must have the same size: ",
300 std::string ValidateAutomatonConstraint(
const CpModelProto&
model,
301 const ConstraintProto&
ct) {
302 const int num_transistions =
ct.automaton().transition_tail().size();
303 if (num_transistions !=
ct.automaton().transition_head().size() ||
304 num_transistions !=
ct.automaton().transition_label().size()) {
306 "The transitions repeated fields must have the same size: ",
312 std::string ValidateReservoirConstraint(
const CpModelProto&
model,
313 const ConstraintProto&
ct) {
314 if (
ct.enforcement_literal_size() > 0) {
315 return "Reservoir does not support enforcement literals.";
317 if (
ct.reservoir().times().size() !=
ct.reservoir().demands().size()) {
318 return absl::StrCat(
"Times and demands fields must be of the same size: ",
321 if (
ct.reservoir().min_level() > 0) {
323 "The min level of a reservoir must be <= 0. Please use fixed events to "
324 "setup initial state: ",
327 if (
ct.reservoir().max_level() < 0) {
329 "The max level of a reservoir must be >= 0. Please use fixed events to "
330 "setup initial state: ",
335 for (
const int64_t
demand :
ct.reservoir().demands()) {
338 return "Possible integer overflow in constraint: " +
342 if (
ct.reservoir().actives_size() > 0 &&
343 ct.reservoir().actives_size() !=
ct.reservoir().times_size()) {
344 return "Wrong array length of actives variables";
346 if (
ct.reservoir().demands_size() > 0 &&
347 ct.reservoir().demands_size() !=
ct.reservoir().times_size()) {
348 return "Wrong array length of demands variables";
353 std::string ValidateIntModConstraint(
const CpModelProto&
model,
354 const ConstraintProto&
ct) {
355 if (
ct.int_mod().vars().size() != 2) {
356 return absl::StrCat(
"An int_mod constraint should have exactly 2 terms: ",
359 const int mod_var =
ct.int_mod().vars(1);
360 const IntegerVariableProto& mod_proto =
model.variables(
PositiveRef(mod_var));
364 "An int_mod must have a strictly positive modulo argument: ",
370 std::string ValidateIntDivConstraint(
const CpModelProto&
model,
371 const ConstraintProto&
ct) {
372 if (
ct.int_div().vars().size() != 2) {
373 return absl::StrCat(
"An int_div constraint should have exactly 2 terms: ",
379 std::string ValidateObjective(
const CpModelProto&
model,
380 const CpObjectiveProto& obj) {
381 if (!DomainInProtoIsValid(obj)) {
382 return absl::StrCat(
"The objective has and invalid domain() format: ",
385 if (obj.vars().size() != obj.coeffs().size()) {
386 return absl::StrCat(
"vars and coeffs size do not match in objective: ",
389 for (
const int v : obj.vars()) {
390 if (!VariableReferenceIsValid(
model, v)) {
391 return absl::StrCat(
"Out of bound integer variable ", v,
395 if (PossibleIntegerOverflow(
model, obj)) {
396 return "Possible integer overflow in objective: " +
402 std::string ValidateSearchStrategies(
const CpModelProto&
model) {
403 for (
const DecisionStrategyProto& strategy :
model.search_strategy()) {
404 const int vss = strategy.variable_selection_strategy();
405 if (vss != DecisionStrategyProto::CHOOSE_FIRST &&
406 vss != DecisionStrategyProto::CHOOSE_LOWEST_MIN &&
407 vss != DecisionStrategyProto::CHOOSE_HIGHEST_MAX &&
408 vss != DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE &&
409 vss != DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE) {
411 "Unknown or unsupported variable_selection_strategy: ", vss);
413 const int drs = strategy.domain_reduction_strategy();
414 if (drs != DecisionStrategyProto::SELECT_MIN_VALUE &&
415 drs != DecisionStrategyProto::SELECT_MAX_VALUE &&
416 drs != DecisionStrategyProto::SELECT_LOWER_HALF &&
417 drs != DecisionStrategyProto::SELECT_UPPER_HALF &&
418 drs != DecisionStrategyProto::SELECT_MEDIAN_VALUE) {
419 return absl::StrCat(
"Unknown or unsupported domain_reduction_strategy: ",
422 for (
const int ref : strategy.variables()) {
423 if (!VariableReferenceIsValid(
model, ref)) {
424 return absl::StrCat(
"Invalid variable reference in strategy: ",
428 int previous_index = -1;
429 for (
const auto& transformation : strategy.transformations()) {
430 if (transformation.positive_coeff() <= 0) {
431 return absl::StrCat(
"Affine transformation coeff should be positive: ",
434 if (transformation.index() <= previous_index ||
435 transformation.index() >= strategy.variables_size()) {
437 "Invalid indices (must be sorted and valid) in transformation: ",
440 previous_index = transformation.index();
446 std::string ValidateSolutionHint(
const CpModelProto&
model) {
447 if (!
model.has_solution_hint())
return "";
448 const auto& hint =
model.solution_hint();
449 if (hint.vars().size() != hint.values().size()) {
450 return "Invalid solution hint: vars and values do not have the same size.";
452 for (
const int ref : hint.vars()) {
453 if (!VariableReferenceIsValid(
model, ref)) {
454 return absl::StrCat(
"Invalid variable reference in solution hint: ", ref);
463 for (
int v = 0; v <
model.variables_size(); ++v) {
466 for (
int c = 0; c <
model.constraints_size(); ++c) {
471 bool support_enforcement =
false;
475 const ConstraintProto&
ct =
model.constraints(c);
476 const ConstraintProto::ConstraintCase type =
ct.constraint_case();
478 case ConstraintProto::ConstraintCase::kIntDiv:
481 case ConstraintProto::ConstraintCase::kIntMod:
484 case ConstraintProto::ConstraintCase::kTable:
487 case ConstraintProto::ConstraintCase::kBoolOr:
488 support_enforcement =
true;
490 case ConstraintProto::ConstraintCase::kBoolAnd:
491 support_enforcement =
true;
493 case ConstraintProto::ConstraintCase::kLinear:
494 support_enforcement =
true;
495 if (!DomainInProtoIsValid(
ct.linear())) {
496 return absl::StrCat(
"Invalid domain in constraint #", c,
" : ",
499 if (
ct.linear().coeffs_size() !=
ct.linear().vars_size()) {
500 return absl::StrCat(
"coeffs_size() != vars_size() in constraint #", c,
505 case ConstraintProto::ConstraintCase::kLinMax: {
506 const std::string target_error =
507 ValidateLinearExpression(
model,
ct.lin_max().target());
508 if (!target_error.empty())
return target_error;
509 for (
int i = 0; i <
ct.lin_max().exprs_size(); ++i) {
510 const std::string expr_error =
511 ValidateLinearExpression(
model,
ct.lin_max().exprs(i));
512 if (!expr_error.empty())
return expr_error;
516 case ConstraintProto::ConstraintCase::kLinMin: {
517 const std::string target_error =
518 ValidateLinearExpression(
model,
ct.lin_min().target());
519 if (!target_error.empty())
return target_error;
520 for (
int i = 0; i <
ct.lin_min().exprs_size(); ++i) {
521 const std::string expr_error =
522 ValidateLinearExpression(
model,
ct.lin_min().exprs(i));
523 if (!expr_error.empty())
return expr_error;
527 case ConstraintProto::ConstraintCase::kInterval:
528 support_enforcement =
true;
531 case ConstraintProto::ConstraintCase::kCumulative:
532 if (
ct.cumulative().intervals_size() !=
533 ct.cumulative().demands_size()) {
535 "intervals_size() != demands_size() in constraint #", c,
" : ",
539 case ConstraintProto::ConstraintCase::kInverse:
540 if (
ct.inverse().f_direct().size() !=
ct.inverse().f_inverse().size()) {
541 return absl::StrCat(
"Non-matching fields size in inverse: ",
545 case ConstraintProto::ConstraintCase::kAutomaton:
548 case ConstraintProto::ConstraintCase::kCircuit:
551 case ConstraintProto::ConstraintCase::kRoutes:
554 case ConstraintProto::ConstraintCase::kNoOverlap2D:
557 case ConstraintProto::ConstraintCase::kReservoir:
567 if (!support_enforcement && !
ct.enforcement_literal().empty()) {
568 for (
const int ref :
ct.enforcement_literal()) {
571 if (domain.
Size() != 1) {
573 "Enforcement literal not supported in constraint: ",
579 if (
model.has_objective()) {
584 for (
const int ref :
model.assumptions()) {
585 if (!LiteralReferenceIsValid(
model, ref)) {
586 return absl::StrCat(
"Invalid literal reference ", ref,
587 " in the 'assumptions' field.");
593 #undef RETURN_IF_NOT_EMPTY
601 class ConstraintChecker {
603 explicit ConstraintChecker(
const std::vector<int64_t>& variable_values)
604 : variable_values_(variable_values) {}
606 bool LiteralIsTrue(
int l)
const {
607 if (l >= 0)
return variable_values_[l] != 0;
608 return variable_values_[-l - 1] == 0;
611 bool LiteralIsFalse(
int l)
const {
return !LiteralIsTrue(l); }
614 if (
var >= 0)
return variable_values_[
var];
615 return -variable_values_[-
var - 1];
618 bool ConstraintIsEnforced(
const ConstraintProto&
ct) {
619 for (
const int lit :
ct.enforcement_literal()) {
620 if (LiteralIsFalse(lit))
return false;
625 bool BoolOrConstraintIsFeasible(
const ConstraintProto&
ct) {
626 for (
const int lit :
ct.bool_or().literals()) {
627 if (LiteralIsTrue(lit))
return true;
632 bool BoolAndConstraintIsFeasible(
const ConstraintProto&
ct) {
633 for (
const int lit :
ct.bool_and().literals()) {
634 if (LiteralIsFalse(lit))
return false;
639 bool AtMostOneConstraintIsFeasible(
const ConstraintProto&
ct) {
640 int num_true_literals = 0;
641 for (
const int lit :
ct.at_most_one().literals()) {
642 if (LiteralIsTrue(lit)) ++num_true_literals;
644 return num_true_literals <= 1;
647 bool ExactlyOneConstraintIsFeasible(
const ConstraintProto&
ct) {
648 int num_true_literals = 0;
649 for (
const int lit :
ct.exactly_one().literals()) {
650 if (LiteralIsTrue(lit)) ++num_true_literals;
652 return num_true_literals == 1;
655 bool BoolXorConstraintIsFeasible(
const ConstraintProto&
ct) {
657 for (
const int lit :
ct.bool_xor().literals()) {
658 sum ^= LiteralIsTrue(lit) ? 1 : 0;
663 bool LinearConstraintIsFeasible(
const ConstraintProto&
ct) {
665 const int num_variables =
ct.linear().coeffs_size();
666 for (
int i = 0; i < num_variables; ++i) {
667 sum +=
Value(
ct.linear().vars(i)) *
ct.linear().coeffs(i);
672 bool IntMaxConstraintIsFeasible(
const ConstraintProto&
ct) {
673 const int64_t
max =
Value(
ct.int_max().target());
675 for (
int i = 0; i <
ct.int_max().vars_size(); ++i) {
678 return max == actual_max;
681 int64_t LinearExpressionValue(
const LinearExpressionProto& expr)
const {
682 int64_t sum = expr.offset();
683 const int num_variables = expr.vars_size();
684 for (
int i = 0; i < num_variables; ++i) {
685 sum +=
Value(expr.vars(i)) * expr.coeffs(i);
690 bool LinMaxConstraintIsFeasible(
const ConstraintProto&
ct) {
691 const int64_t
max = LinearExpressionValue(
ct.lin_max().target());
693 for (
int i = 0; i <
ct.lin_max().exprs_size(); ++i) {
694 const int64_t expr_value = LinearExpressionValue(
ct.lin_max().exprs(i));
695 actual_max =
std::max(actual_max, expr_value);
697 return max == actual_max;
700 bool IntProdConstraintIsFeasible(
const ConstraintProto&
ct) {
701 const int64_t prod =
Value(
ct.int_prod().target());
702 int64_t actual_prod = 1;
703 for (
int i = 0; i <
ct.int_prod().vars_size(); ++i) {
704 actual_prod *=
Value(
ct.int_prod().vars(i));
706 return prod == actual_prod;
709 bool IntDivConstraintIsFeasible(
const ConstraintProto&
ct) {
710 return Value(
ct.int_div().target()) ==
714 bool IntModConstraintIsFeasible(
const ConstraintProto&
ct) {
715 return Value(
ct.int_mod().target()) ==
719 bool IntMinConstraintIsFeasible(
const ConstraintProto&
ct) {
720 const int64_t
min =
Value(
ct.int_min().target());
722 for (
int i = 0; i <
ct.int_min().vars_size(); ++i) {
725 return min == actual_min;
728 bool LinMinConstraintIsFeasible(
const ConstraintProto&
ct) {
729 const int64_t
min = LinearExpressionValue(
ct.lin_min().target());
731 for (
int i = 0; i <
ct.lin_min().exprs_size(); ++i) {
732 const int64_t expr_value = LinearExpressionValue(
ct.lin_min().exprs(i));
733 actual_min =
std::min(actual_min, expr_value);
735 return min == actual_min;
738 bool AllDiffConstraintIsFeasible(
const ConstraintProto&
ct) {
739 absl::flat_hash_set<int64_t> values;
740 for (
const int v :
ct.all_diff().vars()) {
742 values.insert(
Value(v));
747 int64_t IntervalStart(
const IntervalConstraintProto&
interval)
const {
749 ? LinearExpressionValue(
interval.start_view())
753 int64_t IntervalSize(
const IntervalConstraintProto&
interval)
const {
755 ? LinearExpressionValue(
interval.size_view())
759 int64_t IntervalEnd(
const IntervalConstraintProto&
interval)
const {
764 bool IntervalConstraintIsFeasible(
const ConstraintProto&
ct) {
765 const int64_t size = IntervalSize(
ct.interval());
766 if (size < 0)
return false;
767 return IntervalStart(
ct.interval()) + size == IntervalEnd(
ct.interval());
770 bool NoOverlapConstraintIsFeasible(
const CpModelProto&
model,
771 const ConstraintProto&
ct) {
772 std::vector<std::pair<int64_t, int64_t>> start_durations_pairs;
773 for (
const int i :
ct.no_overlap().intervals()) {
774 const ConstraintProto& interval_constraint =
model.constraints(i);
775 if (ConstraintIsEnforced(interval_constraint)) {
776 const IntervalConstraintProto&
interval =
777 interval_constraint.interval();
778 start_durations_pairs.push_back(
782 std::sort(start_durations_pairs.begin(), start_durations_pairs.end());
784 for (
const auto pair : start_durations_pairs) {
785 if (pair.first < previous_end)
return false;
786 previous_end = pair.first + pair.second;
791 bool IntervalsAreDisjoint(
const IntervalConstraintProto& interval1,
792 const IntervalConstraintProto& interval2) {
793 return IntervalEnd(interval1) <= IntervalStart(interval2) ||
794 IntervalEnd(interval2) <= IntervalStart(interval1);
797 bool IntervalIsEmpty(
const IntervalConstraintProto&
interval) {
801 bool NoOverlap2DConstraintIsFeasible(
const CpModelProto&
model,
802 const ConstraintProto&
ct) {
803 const auto& arg =
ct.no_overlap_2d();
806 std::vector<std::pair<
const IntervalConstraintProto*
const,
807 const IntervalConstraintProto*
const>>
808 enforced_intervals_xy;
810 const int num_intervals = arg.x_intervals_size();
811 CHECK_EQ(arg.y_intervals_size(), num_intervals);
812 for (
int i = 0; i < num_intervals; ++i) {
813 const ConstraintProto& x =
model.constraints(arg.x_intervals(i));
814 const ConstraintProto& y =
model.constraints(arg.y_intervals(i));
815 if (ConstraintIsEnforced(x) && ConstraintIsEnforced(y) &&
816 (!arg.boxes_with_null_area_can_overlap() ||
817 (!IntervalIsEmpty(x.interval()) &&
818 !IntervalIsEmpty(y.interval())))) {
819 enforced_intervals_xy.push_back({&x.interval(), &y.interval()});
823 const int num_enforced_intervals = enforced_intervals_xy.size();
824 for (
int i = 0; i < num_enforced_intervals; ++i) {
825 for (
int j = i + 1; j < num_enforced_intervals; ++j) {
826 const auto& xi = *enforced_intervals_xy[i].first;
827 const auto& yi = *enforced_intervals_xy[i].second;
828 const auto& xj = *enforced_intervals_xy[j].first;
829 const auto& yj = *enforced_intervals_xy[j].second;
830 if (!IntervalsAreDisjoint(xi, xj) && !IntervalsAreDisjoint(yi, yj) &&
831 !IntervalIsEmpty(xi) && !IntervalIsEmpty(xj) &&
832 !IntervalIsEmpty(yi) && !IntervalIsEmpty(yj)) {
833 VLOG(1) <<
"Interval " << i <<
"(x=[" << IntervalStart(xi) <<
", "
834 << IntervalEnd(xi) <<
"], y=[" << IntervalStart(yi) <<
", "
835 << IntervalEnd(yi) <<
"]) and " << j <<
"(x=["
836 << IntervalStart(xj) <<
", " << IntervalEnd(xj) <<
"], y=["
837 << IntervalStart(yj) <<
", " << IntervalEnd(yj)
838 <<
"]) are not disjoint.";
846 bool CumulativeConstraintIsFeasible(
const CpModelProto&
model,
847 const ConstraintProto&
ct) {
850 const int num_intervals =
ct.cumulative().intervals_size();
851 absl::flat_hash_map<int64_t, int64_t> usage;
852 for (
int i = 0; i < num_intervals; ++i) {
853 const ConstraintProto& interval_constraint =
854 model.constraints(
ct.cumulative().intervals(i));
855 if (ConstraintIsEnforced(interval_constraint)) {
856 const IntervalConstraintProto&
interval =
857 interval_constraint.interval();
858 const int64_t start = IntervalStart(
interval);
859 const int64_t duration = IntervalSize(
interval);
861 for (int64_t t = start; t < start + duration; ++t) {
863 if (usage[t] >
capacity)
return false;
870 bool ElementConstraintIsFeasible(
const ConstraintProto&
ct) {
875 bool TableConstraintIsFeasible(
const ConstraintProto&
ct) {
876 const int size =
ct.table().vars_size();
877 if (size == 0)
return true;
878 for (
int row_start = 0; row_start <
ct.table().values_size();
881 while (
Value(
ct.table().vars(i)) ==
ct.table().values(row_start + i)) {
883 if (i == size)
return !
ct.table().negated();
886 return ct.table().negated();
889 bool AutomatonConstraintIsFeasible(
const ConstraintProto&
ct) {
891 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> transition_map;
892 const int num_transitions =
ct.automaton().transition_tail().size();
893 for (
int i = 0; i < num_transitions; ++i) {
894 transition_map[{
ct.automaton().transition_tail(i),
895 ct.automaton().transition_label(i)}] =
896 ct.automaton().transition_head(i);
900 int64_t current_state =
ct.automaton().starting_state();
901 const int num_steps =
ct.automaton().vars_size();
902 for (
int i = 0; i < num_steps; ++i) {
903 const std::pair<int64_t, int64_t> key = {current_state,
904 Value(
ct.automaton().vars(i))};
908 current_state = transition_map[key];
912 for (
const int64_t
final :
ct.automaton().final_states()) {
913 if (current_state ==
final)
return true;
918 bool CircuitConstraintIsFeasible(
const ConstraintProto&
ct) {
921 const int num_arcs =
ct.circuit().tails_size();
922 absl::flat_hash_set<int>
nodes;
923 absl::flat_hash_map<int, int> nexts;
924 for (
int i = 0; i < num_arcs; ++i) {
925 const int tail =
ct.circuit().tails(i);
926 const int head =
ct.circuit().heads(i);
929 if (LiteralIsFalse(
ct.circuit().literals(i)))
continue;
930 if (nexts.contains(
tail))
return false;
937 for (
const int node :
nodes) {
938 if (!nexts.contains(node))
return false;
939 if (nexts[node] == node)
continue;
943 if (cycle_size == 0)
return true;
947 absl::flat_hash_set<int> visited;
948 int current = in_cycle;
950 while (!visited.contains(current)) {
952 visited.insert(current);
953 current = nexts[current];
955 if (current != in_cycle)
return false;
956 return num_visited == cycle_size;
959 bool RoutesConstraintIsFeasible(
const ConstraintProto&
ct) {
960 const int num_arcs =
ct.routes().tails_size();
961 int num_used_arcs = 0;
962 int num_self_arcs = 0;
964 std::vector<int> tail_to_head;
965 std::vector<int> depot_nexts;
966 for (
int i = 0; i < num_arcs; ++i) {
967 const int tail =
ct.routes().tails(i);
968 const int head =
ct.routes().heads(i);
971 tail_to_head.resize(num_nodes, -1);
972 if (LiteralIsTrue(
ct.routes().literals(i))) {
974 if (
tail == 0)
return false;
980 depot_nexts.push_back(
head);
982 if (tail_to_head[
tail] != -1)
return false;
989 if (num_nodes == 0)
return true;
993 for (
int start : depot_nexts) {
996 if (tail_to_head[start] == -1)
return false;
997 start = tail_to_head[start];
1002 if (count != num_used_arcs) {
1003 VLOG(1) <<
"count: " << count <<
" != num_used_arcs:" << num_used_arcs;
1011 if (count - depot_nexts.size() + 1 + num_self_arcs != num_nodes) {
1012 VLOG(1) <<
"Not all nodes are covered!";
1019 bool InverseConstraintIsFeasible(
const ConstraintProto&
ct) {
1020 const int num_variables =
ct.inverse().f_direct_size();
1021 if (num_variables !=
ct.inverse().f_inverse_size())
return false;
1023 for (
int i = 0; i < num_variables; i++) {
1024 const int fi =
Value(
ct.inverse().f_direct(i));
1025 if (fi < 0 || num_variables <= fi)
return false;
1026 if (i !=
Value(
ct.inverse().f_inverse(fi)))
return false;
1031 bool ReservoirConstraintIsFeasible(
const ConstraintProto&
ct) {
1032 const int num_variables =
ct.reservoir().times_size();
1033 const int64_t min_level =
ct.reservoir().min_level();
1034 const int64_t max_level =
ct.reservoir().max_level();
1035 std::map<int64_t, int64_t> deltas;
1037 const bool has_active_variables =
ct.reservoir().actives_size() > 0;
1038 for (
int i = 0; i < num_variables; i++) {
1039 const int64_t
time =
Value(
ct.reservoir().times(i));
1041 VLOG(1) <<
"reservoir times(" << i <<
") is negative.";
1044 if (!has_active_variables ||
Value(
ct.reservoir().actives(i)) == 1) {
1045 deltas[
time] +=
ct.reservoir().demands(i);
1048 int64_t current_level = 0;
1049 for (
const auto&
delta : deltas) {
1050 current_level +=
delta.second;
1051 if (current_level < min_level || current_level > max_level) {
1052 VLOG(1) <<
"Reservoir level " << current_level
1053 <<
" is out of bounds at time" <<
delta.first;
1061 std::vector<int64_t> variable_values_;
1067 const std::vector<int64_t>& variable_values,
1068 const CpModelProto* mapping_proto,
1069 const std::vector<int>* postsolve_mapping) {
1070 if (variable_values.size() !=
model.variables_size()) {
1071 VLOG(1) <<
"Wrong number of variables in the solution vector";
1076 for (
int i = 0; i <
model.variables_size(); ++i) {
1078 VLOG(1) <<
"Variable #" << i <<
" has value " << variable_values[i]
1079 <<
" which do not fall in its domain: "
1086 ConstraintChecker checker(variable_values);
1088 for (
int c = 0; c <
model.constraints_size(); ++c) {
1089 const ConstraintProto&
ct =
model.constraints(c);
1091 if (!checker.ConstraintIsEnforced(
ct))
continue;
1093 bool is_feasible =
true;
1094 const ConstraintProto::ConstraintCase type =
ct.constraint_case();
1096 case ConstraintProto::ConstraintCase::kBoolOr:
1097 is_feasible = checker.BoolOrConstraintIsFeasible(
ct);
1099 case ConstraintProto::ConstraintCase::kBoolAnd:
1100 is_feasible = checker.BoolAndConstraintIsFeasible(
ct);
1102 case ConstraintProto::ConstraintCase::kAtMostOne:
1103 is_feasible = checker.AtMostOneConstraintIsFeasible(
ct);
1105 case ConstraintProto::ConstraintCase::kExactlyOne:
1106 is_feasible = checker.ExactlyOneConstraintIsFeasible(
ct);
1108 case ConstraintProto::ConstraintCase::kBoolXor:
1109 is_feasible = checker.BoolXorConstraintIsFeasible(
ct);
1111 case ConstraintProto::ConstraintCase::kLinear:
1112 is_feasible = checker.LinearConstraintIsFeasible(
ct);
1114 case ConstraintProto::ConstraintCase::kIntProd:
1115 is_feasible = checker.IntProdConstraintIsFeasible(
ct);
1117 case ConstraintProto::ConstraintCase::kIntDiv:
1118 is_feasible = checker.IntDivConstraintIsFeasible(
ct);
1120 case ConstraintProto::ConstraintCase::kIntMod:
1121 is_feasible = checker.IntModConstraintIsFeasible(
ct);
1123 case ConstraintProto::ConstraintCase::kIntMin:
1124 is_feasible = checker.IntMinConstraintIsFeasible(
ct);
1126 case ConstraintProto::ConstraintCase::kLinMin:
1127 is_feasible = checker.LinMinConstraintIsFeasible(
ct);
1129 case ConstraintProto::ConstraintCase::kIntMax:
1130 is_feasible = checker.IntMaxConstraintIsFeasible(
ct);
1132 case ConstraintProto::ConstraintCase::kLinMax:
1133 is_feasible = checker.LinMaxConstraintIsFeasible(
ct);
1135 case ConstraintProto::ConstraintCase::kAllDiff:
1136 is_feasible = checker.AllDiffConstraintIsFeasible(
ct);
1138 case ConstraintProto::ConstraintCase::kInterval:
1139 is_feasible = checker.IntervalConstraintIsFeasible(
ct);
1141 case ConstraintProto::ConstraintCase::kNoOverlap:
1142 is_feasible = checker.NoOverlapConstraintIsFeasible(
model,
ct);
1144 case ConstraintProto::ConstraintCase::kNoOverlap2D:
1145 is_feasible = checker.NoOverlap2DConstraintIsFeasible(
model,
ct);
1147 case ConstraintProto::ConstraintCase::kCumulative:
1148 is_feasible = checker.CumulativeConstraintIsFeasible(
model,
ct);
1150 case ConstraintProto::ConstraintCase::kElement:
1151 is_feasible = checker.ElementConstraintIsFeasible(
ct);
1153 case ConstraintProto::ConstraintCase::kTable:
1154 is_feasible = checker.TableConstraintIsFeasible(
ct);
1156 case ConstraintProto::ConstraintCase::kAutomaton:
1157 is_feasible = checker.AutomatonConstraintIsFeasible(
ct);
1159 case ConstraintProto::ConstraintCase::kCircuit:
1160 is_feasible = checker.CircuitConstraintIsFeasible(
ct);
1162 case ConstraintProto::ConstraintCase::kRoutes:
1163 is_feasible = checker.RoutesConstraintIsFeasible(
ct);
1165 case ConstraintProto::ConstraintCase::kInverse:
1166 is_feasible = checker.InverseConstraintIsFeasible(
ct);
1168 case ConstraintProto::ConstraintCase::kReservoir:
1169 is_feasible = checker.ReservoirConstraintIsFeasible(
ct);
1171 case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1180 VLOG(1) <<
"Failing constraint #" << c <<
" : "
1182 if (mapping_proto !=
nullptr && postsolve_mapping !=
nullptr) {
1183 std::vector<int> reverse_map(mapping_proto->variables().size(), -1);
1184 for (
int var = 0;
var < postsolve_mapping->size(); ++
var) {
1185 reverse_map[(*postsolve_mapping)[
var]] =
var;
1188 VLOG(1) <<
"var: " <<
var <<
" mapped_to: " << reverse_map[
var]
1189 <<
" value: " << variable_values[
var] <<
" initial_domain: "
1191 <<
" postsolved_domain: "
1196 VLOG(1) <<
"var: " <<
var <<
" value: " << variable_values[
var];
#define CHECK_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.
#define RETURN_IF_NOT_EMPTY(statement)
bool ContainsKey(const Collection &collection, const Key &key)
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool RefIsPositive(int ref)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
bool SolutionIsFeasible(const CpModelProto &model, const std::vector< int64_t > &variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
std::string ValidateCpModel(const CpModelProto &model)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
int64_t CapProd(int64_t x, int64_t y)
std::string ProtobufDebugString(const P &message)
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
Returns true iff we have: