25#include "absl/container/btree_map.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/log/check.h"
29#include "absl/meta/type_traits.h"
30#include "absl/strings/str_cat.h"
31#include "absl/types/span.h"
34#include "ortools/sat/cp_model.pb.h"
36#include "ortools/sat/sat_parameters.pb.h"
49#define RETURN_IF_NOT_EMPTY(statement) \
51 const std::string error_message = statement; \
52 if (!error_message.empty()) return error_message; \
55template <
typename ProtoWithDomain>
56bool DomainInProtoIsValid(
const ProtoWithDomain&
proto) {
57 if (
proto.domain().size() % 2)
return false;
58 std::vector<ClosedInterval> domain;
59 for (
int i = 0;
i <
proto.domain_size();
i += 2) {
61 domain.push_back({
proto.domain(
i),
proto.domain(
i + 1)});
66bool VariableReferenceIsValid(
const CpModelProto&
model,
int reference) {
68 if (reference >=
model.variables_size())
return false;
69 return reference >= -
static_cast<int>(
model.variables_size());
76bool VariableIndexIsValid(
const CpModelProto&
model,
int var) {
80bool LiteralReferenceIsValid(
const CpModelProto&
model,
int reference) {
81 if (!VariableReferenceIsValid(
model, reference))
return false;
83 const int64_t min_domain = var_proto.domain(0);
84 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
85 return min_domain >= 0 && max_domain <= 1;
88std::string ValidateIntegerVariable(
const CpModelProto&
model,
int v) {
89 const IntegerVariableProto&
proto =
model.variables(v);
90 if (
proto.domain_size() == 0) {
91 return absl::StrCat(
"var #", v,
94 if (
proto.domain_size() % 2 != 0) {
95 return absl::StrCat(
"var #", v,
" has an odd domain() size: ",
98 if (!DomainInProtoIsValid(
proto)) {
99 return absl::StrCat(
"var #", v,
" has and invalid domain() format: ",
106 const int64_t lb =
proto.domain(0);
107 const int64_t ub =
proto.domain(
proto.domain_size() - 1);
108 if (lb < std::numeric_limits<int64_t>::min() + 2 ||
109 ub > std::numeric_limits<int64_t>::max() - 1) {
111 "var #", v,
" domain do not fall in [kint64min + 2, kint64max - 1]. ",
117 if (lb < 0 && lb + std::numeric_limits<int64_t>::max() < ub) {
120 " has a domain that is too large, i.e. |UB - LB| overflow an int64_t: ",
127std::string ValidateVariablesUsedInConstraint(
const CpModelProto&
model,
129 const ConstraintProto&
ct =
model.constraints(c);
131 for (
const int v : references.variables) {
132 if (!VariableReferenceIsValid(
model, v)) {
133 return absl::StrCat(
"Out of bound integer variable ", v,
134 " in constraint #", c,
" : ",
138 for (
const int lit :
ct.enforcement_literal()) {
139 if (!LiteralReferenceIsValid(
model,
lit)) {
140 return absl::StrCat(
"Invalid enforcement literal ",
lit,
141 " in constraint #", c,
" : ",
145 for (
const int lit : references.literals) {
146 if (!LiteralReferenceIsValid(
model,
lit)) {
147 return absl::StrCat(
"Invalid literal ",
lit,
" in constraint #", c,
" : ",
154std::string ValidateIntervalsUsedInConstraint(
bool after_presolve,
155 const CpModelProto&
model,
157 const ConstraintProto&
ct =
model.constraints(c);
160 return absl::StrCat(
"Out of bound interval ",
i,
" in constraint #", c,
163 if (after_presolve &&
i >= c) {
164 return absl::StrCat(
"Interval ",
i,
" in constraint #", c,
165 " must appear before in the list of constraints :",
168 if (
model.constraints(
i).constraint_case() !=
169 ConstraintProto::ConstraintCase::kInterval) {
172 " does not refer to an interval constraint. Problematic constraint #",
179int64_t MinOfRef(
const CpModelProto&
model,
int ref) {
182 return var_proto.domain(0);
184 return -var_proto.domain(var_proto.domain_size() - 1);
188int64_t MaxOfRef(
const CpModelProto&
model,
int ref) {
191 return var_proto.domain(var_proto.domain_size() - 1);
193 return -var_proto.domain(0);
197template <
class LinearExpressionProto>
198int64_t MinOfExpression(
const CpModelProto&
model,
199 const LinearExpressionProto&
proto) {
200 int64_t sum_min =
proto.offset();
201 for (
int i = 0;
i <
proto.vars_size(); ++
i) {
202 const int ref =
proto.vars(
i);
203 const int64_t coeff =
proto.coeffs(
i);
212template <
class LinearExpressionProto>
213int64_t MaxOfExpression(
const CpModelProto&
model,
214 const LinearExpressionProto&
proto) {
215 int64_t sum_max =
proto.offset();
216 for (
int i = 0;
i <
proto.vars_size(); ++
i) {
217 const int ref =
proto.vars(
i);
218 const int64_t coeff =
proto.coeffs(
i);
227bool ExpressionIsFixed(
const CpModelProto&
model,
228 const LinearExpressionProto& expr) {
229 for (
int i = 0;
i < expr.vars_size(); ++
i) {
230 if (expr.coeffs(
i) == 0)
continue;
231 const IntegerVariableProto& var_proto =
model.variables(expr.vars(
i));
232 if (var_proto.domain_size() != 2 ||
233 var_proto.domain(0) != var_proto.domain(1)) {
240int64_t ExpressionFixedValue(
const CpModelProto&
model,
241 const LinearExpressionProto& expr) {
242 DCHECK(ExpressionIsFixed(
model, expr));
243 return MinOfExpression(
model, expr);
247 DCHECK_EQ(ConstraintProto::ConstraintCase::kInterval,
249 const IntervalConstraintProto&
proto =
254Domain DomainOfRef(
const CpModelProto&
model,
int ref) {
260 const LinearExpressionProto& expr) {
261 if (expr.coeffs_size() != expr.vars_size()) {
262 return absl::StrCat(
"coeffs_size() != vars_size() in linear expression: ",
267 return absl::StrCat(
"Possible overflow in linear expression: ",
270 for (
const int var : expr.vars()) {
272 return absl::StrCat(
"Invalid negated variable in linear expression: ",
279std::string ValidateAffineExpression(
const CpModelProto&
model,
280 const LinearExpressionProto& expr) {
281 if (expr.vars_size() > 1) {
282 return absl::StrCat(
"expression must be affine: ",
288std::string ValidateConstantAffineExpression(
289 const CpModelProto&
model,
const LinearExpressionProto& expr) {
290 if (!expr.vars().empty()) {
291 return absl::StrCat(
"expression must be constant: ",
297std::string ValidateLinearConstraint(
const CpModelProto&
model,
298 const ConstraintProto&
ct) {
299 if (!DomainInProtoIsValid(
ct.linear())) {
300 return absl::StrCat(
"Invalid domain in constraint : ",
303 if (
ct.linear().coeffs_size() !=
ct.linear().vars_size()) {
304 return absl::StrCat(
"coeffs_size() != vars_size() in constraint: ",
307 for (
const int var :
ct.linear().vars()) {
309 return absl::StrCat(
"Invalid negated variable in linear constraint: ",
313 const LinearConstraintProto& arg =
ct.linear();
315 return "Possible integer overflow in constraint: " +
321std::string ValidateIntModConstraint(
const CpModelProto&
model,
322 const ConstraintProto&
ct) {
323 if (
ct.int_mod().exprs().size() != 2) {
324 return absl::StrCat(
"An int_mod constraint should have exactly 2 terms: ",
327 if (!
ct.int_mod().has_target()) {
328 return absl::StrCat(
"An int_mod constraint should have a target: ",
336 const LinearExpressionProto mod_expr =
ct.int_mod().exprs(1);
337 if (MinOfExpression(
model, mod_expr) <= 0) {
339 "An int_mod must have a strictly positive modulo argument: ",
346std::string ValidateIntProdConstraint(
const CpModelProto&
model,
347 const ConstraintProto&
ct) {
348 if (!
ct.int_prod().has_target()) {
349 return absl::StrCat(
"An int_prod constraint should have a target: ",
353 for (
const LinearExpressionProto& expr :
ct.int_prod().exprs()) {
359 Domain product_domain(1);
360 for (
const LinearExpressionProto& expr :
ct.int_prod().exprs()) {
361 product_domain = product_domain.ContinuousMultiplicationBy(
362 {MinOfExpression(
model, expr), MaxOfExpression(
model, expr)});
365 if (product_domain.Max() <= -std ::numeric_limits<int64_t>::max() ||
366 product_domain.Min() >= std::numeric_limits<int64_t>::max()) {
367 return absl::StrCat(
"integer overflow in constraint: ",
373 if (
ct.int_prod().exprs_size() > 2 &&
374 (product_domain.Max() >= std ::numeric_limits<int64_t>::max() ||
375 product_domain.Min() <= -std::numeric_limits<int64_t>::max())) {
376 return absl::StrCat(
"Potential integer overflow in constraint: ",
383std::string ValidateIntDivConstraint(
const CpModelProto&
model,
384 const ConstraintProto&
ct) {
385 if (
ct.int_div().exprs().size() != 2) {
386 return absl::StrCat(
"An int_div constraint should have exactly 2 terms: ",
389 if (!
ct.int_div().has_target()) {
390 return absl::StrCat(
"An int_div constraint should have a target: ",
398 const LinearExpressionProto& denom =
ct.int_div().exprs(1);
399 const int64_t offset = denom.offset();
400 if (ExpressionIsFixed(
model, denom)) {
401 if (ExpressionFixedValue(
model, denom) == 0) {
405 const int64_t coeff = denom.coeffs(0);
407 const int64_t inverse_of_zero = -offset / coeff;
408 if (inverse_of_zero * coeff + offset == 0 &&
409 DomainOfRef(
model, denom.vars(0)).Contains(inverse_of_zero)) {
410 return absl::StrCat(
"The domain of the divisor cannot contain 0: ",
417std::string ValidateElementConstraint(
const CpModelProto&
model,
418 const ConstraintProto&
ct) {
419 const ElementConstraintProto& element =
ct.element();
423 LinearExpressionProto overflow_detection;
424 overflow_detection.add_vars(element.target());
425 overflow_detection.add_coeffs(1);
426 overflow_detection.add_vars( 0);
427 overflow_detection.add_coeffs(-1);
428 for (
const int ref : element.vars()) {
429 overflow_detection.set_vars(1, ref);
431 overflow_detection.coeffs())) {
433 "Domain of the variables involved in element constraint may cause "
441std::string ValidateTableConstraint(
const ConstraintProto&
ct) {
442 const TableConstraintProto& arg =
ct.table();
443 if (arg.vars().empty())
return "";
444 if (arg.values().size() % arg.vars().size() != 0) {
446 "The flat encoding of a table constraint must be a multiple of the "
447 "number of variable: ",
453std::string ValidateAutomatonConstraint(
const ConstraintProto&
ct) {
454 const int num_transistions =
ct.automaton().transition_tail().size();
455 if (num_transistions !=
ct.automaton().transition_head().size() ||
456 num_transistions !=
ct.automaton().transition_label().size()) {
458 "The transitions repeated fields must have the same size: ",
461 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> tail_label_to_head;
462 for (
int i = 0;
i < num_transistions; ++
i) {
463 const int64_t
tail =
ct.automaton().transition_tail(
i);
464 const int64_t
head =
ct.automaton().transition_head(
i);
465 const int64_t label =
ct.automaton().transition_label(
i);
466 if (label <= std::numeric_limits<int64_t>::min() + 1 ||
467 label == std::numeric_limits<int64_t>::max()) {
468 return absl::StrCat(
"labels in the automaton constraint are too big: ",
471 const auto [it, inserted] =
472 tail_label_to_head.insert({{
tail, label},
head});
474 if (it->second ==
head) {
475 return absl::StrCat(
"automaton: duplicate transition ",
tail,
" --(",
476 label,
")--> ",
head);
478 return absl::StrCat(
"automaton: incompatible transitions ",
tail,
479 " --(", label,
")--> ",
head,
" and ",
tail,
" --(",
480 label,
")--> ", it->second);
487template <
typename GraphProto>
488std::string ValidateGraphInput(
bool is_route,
const GraphProto&
graph) {
491 return absl::StrCat(
"Wrong field sizes in graph: ",
496 absl::flat_hash_set<int> self_loops;
497 for (
int i = 0;
i <
size; ++
i) {
499 if (!self_loops.insert(
graph.heads(
i)).second) {
501 "Circuit/Route constraint contains multiple self-loop involving "
505 if (is_route &&
graph.tails(
i) == 0) {
507 "A route constraint cannot have a self-loop on the depot (node 0)");
514std::string ValidateRoutesConstraint(
const ConstraintProto&
ct) {
516 absl::flat_hash_set<int>
nodes;
517 for (
const int node :
ct.routes().tails()) {
519 return "All node in a route constraint must be in [0, num_nodes)";
522 max_node = std::max(max_node, node);
524 for (
const int node :
ct.routes().heads()) {
526 return "All node in a route constraint must be in [0, num_nodes)";
529 max_node = std::max(max_node, node);
531 if (!
nodes.empty() && max_node !=
nodes.size() - 1) {
533 "All nodes in a route constraint must have incident arcs");
536 return ValidateGraphInput(
true,
ct.routes());
539void AppendToOverflowValidator(
const LinearExpressionProto&
input,
540 LinearExpressionProto* output) {
541 output->mutable_vars()->Add(
input.vars().begin(),
input.vars().end());
542 output->mutable_coeffs()->Add(
input.coeffs().begin(),
input.coeffs().end());
547 CapAdd(std::abs(output->offset()), std::abs(
input.offset())));
550std::string ValidateIntervalConstraint(
const CpModelProto&
model,
551 const ConstraintProto&
ct) {
552 if (
ct.enforcement_literal().size() > 1) {
554 "Interval with more than one enforcement literals are currently not "
558 const IntervalConstraintProto& arg =
ct.interval();
560 if (!arg.has_start()) {
561 return absl::StrCat(
"Interval must have a start expression: ",
564 if (!arg.has_size()) {
565 return absl::StrCat(
"Interval must have a size expression: ",
568 if (!arg.has_end()) {
569 return absl::StrCat(
"Interval must have a end expression: ",
573 LinearExpressionProto for_overflow_validation;
574 if (arg.start().vars_size() > 1) {
575 return "Interval with a start expression containing more than one "
576 "variable are currently not supported.";
579 AppendToOverflowValidator(arg.start(), &for_overflow_validation);
580 if (arg.size().vars_size() > 1) {
581 return "Interval with a size expression containing more than one "
582 "variable are currently not supported.";
585 if (
ct.enforcement_literal().empty() &&
586 MinOfExpression(
model, arg.size()) < 0) {
588 "The size of a performed interval must be >= 0 in constraint: ",
591 AppendToOverflowValidator(arg.size(), &for_overflow_validation);
592 if (arg.end().vars_size() > 1) {
593 return "Interval with a end expression containing more than one "
594 "variable are currently not supported.";
597 AppendToOverflowValidator(arg.end(), &for_overflow_validation);
600 for_overflow_validation.coeffs(),
601 for_overflow_validation.offset())) {
602 return absl::StrCat(
"Possible overflow in interval: ",
609std::string ValidateCumulativeConstraint(
const CpModelProto&
model,
610 const ConstraintProto&
ct) {
611 if (
ct.cumulative().intervals_size() !=
ct.cumulative().demands_size()) {
612 return absl::StrCat(
"intervals_size() != demands_size() in constraint: ",
618 for (
const LinearExpressionProto&
demand :
ct.cumulative().demands()) {
622 for (
const LinearExpressionProto& demand_expr :
ct.cumulative().demands()) {
623 if (MinOfExpression(
model, demand_expr) < 0) {
628 if (demand_expr.vars_size() > 1) {
630 " must be affine or constant in constraint: ",
634 if (
ct.cumulative().capacity().vars_size() > 1) {
640 int64_t sum_max_demands = 0;
641 for (
const LinearExpressionProto& demand_expr :
ct.cumulative().demands()) {
642 const int64_t demand_max = MaxOfExpression(
model, demand_expr);
643 DCHECK_GE(demand_max, 0);
644 sum_max_demands =
CapAdd(sum_max_demands, demand_max);
645 if (sum_max_demands == std::numeric_limits<int64_t>::max()) {
646 return "The sum of max demands do not fit on an int64_t in constraint: " +
654std::string ValidateNoOverlap2DConstraint(
const CpModelProto&
model,
655 const ConstraintProto&
ct) {
656 const int size_x =
ct.no_overlap_2d().x_intervals().size();
657 const int size_y =
ct.no_overlap_2d().y_intervals().size();
658 if (size_x != size_y) {
659 return absl::StrCat(
"The two lists of intervals must have the same size: ",
664 int64_t sum_max_areas = 0;
665 for (
int i = 0;
i <
ct.no_overlap_2d().x_intervals().
size(); ++
i) {
666 const int64_t max_size_x =
667 IntervalSizeMax(
model,
ct.no_overlap_2d().x_intervals(
i));
668 const int64_t max_size_y =
669 IntervalSizeMax(
model,
ct.no_overlap_2d().y_intervals(
i));
670 sum_max_areas =
CapAdd(sum_max_areas,
CapProd(max_size_x, max_size_y));
671 if (sum_max_areas == std::numeric_limits<int64_t>::max()) {
672 return "Integer overflow when summing all areas in "
680std::string ValidateReservoirConstraint(
const CpModelProto&
model,
681 const ConstraintProto&
ct) {
682 if (
ct.enforcement_literal_size() > 0) {
683 return "Reservoir does not support enforcement literals.";
685 if (
ct.reservoir().time_exprs().size() !=
686 ct.reservoir().level_changes().size()) {
688 "time_exprs and level_changes fields must be of the same size: ",
691 for (
const LinearExpressionProto& expr :
ct.reservoir().time_exprs()) {
694 for (
const LinearExpressionProto& expr :
ct.reservoir().level_changes()) {
697 if (
ct.reservoir().min_level() > 0) {
699 "The min level of a reservoir must be <= 0. Please use fixed events to "
700 "setup initial state: ",
703 if (
ct.reservoir().max_level() < 0) {
705 "The max level of a reservoir must be >= 0. Please use fixed events to "
706 "setup initial state: ",
711 for (
const LinearExpressionProto&
demand :
ct.reservoir().level_changes()) {
713 const int64_t demand_min = MinOfExpression(
model,
demand);
714 const int64_t demand_max = MaxOfExpression(
model,
demand);
716 if (sum_abs == std::numeric_limits<int64_t>::max()) {
717 return "Possible integer overflow in constraint: " +
721 if (
ct.reservoir().active_literals_size() > 0 &&
722 ct.reservoir().active_literals_size() !=
723 ct.reservoir().time_exprs_size()) {
724 return "Wrong array length of active_literals variables";
726 if (
ct.reservoir().level_changes_size() > 0 &&
727 ct.reservoir().level_changes_size() !=
ct.reservoir().time_exprs_size()) {
728 return "Wrong array length of level_changes variables";
733std::string ValidateObjective(
const CpModelProto&
model,
734 const CpObjectiveProto& obj) {
735 if (!DomainInProtoIsValid(obj)) {
736 return absl::StrCat(
"The objective has and invalid domain() format: ",
739 if (obj.vars().size() != obj.coeffs().size()) {
740 return absl::StrCat(
"vars and coeffs size do not match in objective: ",
743 for (
const int v : obj.vars()) {
744 if (!VariableReferenceIsValid(
model, v)) {
745 return absl::StrCat(
"Out of bound integer variable ", v,
750 return "Possible integer overflow in objective: " +
756std::string ValidateFloatingPointObjective(
double max_valid_magnitude,
757 const CpModelProto&
model,
758 const FloatObjectiveProto& obj) {
759 if (obj.vars().size() != obj.coeffs().size()) {
760 return absl::StrCat(
"vars and coeffs size do not match in objective: ",
763 for (
const int v : obj.vars()) {
764 if (!VariableIndexIsValid(
model, v)) {
765 return absl::StrCat(
"Out of bound integer variable ", v,
769 for (
const double coeff : obj.coeffs()) {
770 if (!std::isfinite(coeff)) {
771 return absl::StrCat(
"Coefficients must be finite in objective: ",
774 if (std::abs(coeff) > max_valid_magnitude) {
776 "Coefficients larger than params.mip_max_valid_magnitude() [value = ",
781 if (!std::isfinite(obj.offset())) {
782 return absl::StrCat(
"Offset must be finite in objective: ",
788std::string ValidateSearchStrategies(
const CpModelProto&
model) {
789 for (
const DecisionStrategyProto& strategy :
model.search_strategy()) {
790 const int vss = strategy.variable_selection_strategy();
791 if (vss != DecisionStrategyProto::CHOOSE_FIRST &&
792 vss != DecisionStrategyProto::CHOOSE_LOWEST_MIN &&
793 vss != DecisionStrategyProto::CHOOSE_HIGHEST_MAX &&
794 vss != DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE &&
795 vss != DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE) {
797 "Unknown or unsupported variable_selection_strategy: ", vss);
799 const int drs = strategy.domain_reduction_strategy();
800 if (drs != DecisionStrategyProto::SELECT_MIN_VALUE &&
801 drs != DecisionStrategyProto::SELECT_MAX_VALUE &&
802 drs != DecisionStrategyProto::SELECT_LOWER_HALF &&
803 drs != DecisionStrategyProto::SELECT_UPPER_HALF &&
804 drs != DecisionStrategyProto::SELECT_MEDIAN_VALUE) {
805 return absl::StrCat(
"Unknown or unsupported domain_reduction_strategy: ",
808 if (!strategy.variables().empty() && !strategy.exprs().empty()) {
809 return absl::StrCat(
"Strategy can't have both variables and exprs: ",
812 for (
const int ref : strategy.variables()) {
813 if (!VariableReferenceIsValid(
model, ref)) {
814 return absl::StrCat(
"Invalid variable reference in strategy: ",
817 if (drs == DecisionStrategyProto::SELECT_MEDIAN_VALUE &&
820 return absl::StrCat(
"Variable #",
PositiveRef(ref),
821 " has a domain too large to be used in a"
822 " SELECT_MEDIAN_VALUE value selection strategy");
825 for (
const LinearExpressionProto& expr : strategy.exprs()) {
826 for (
const int var : expr.vars()) {
827 if (!VariableReferenceIsValid(
model,
var)) {
828 return absl::StrCat(
"Invalid variable reference in strategy: ",
832 if (!ValidateAffineExpression(
model, expr).empty()) {
833 return absl::StrCat(
"Invalid affine expr in strategy: ",
836 if (drs == DecisionStrategyProto::SELECT_MEDIAN_VALUE) {
837 for (
const int var : expr.vars()) {
841 " has a domain too large to be used in a"
842 " SELECT_MEDIAN_VALUE value selection strategy");
851std::string ValidateSolutionHint(
const CpModelProto&
model) {
852 if (!
model.has_solution_hint())
return "";
853 const auto&
hint =
model.solution_hint();
854 if (
hint.vars().size() !=
hint.values().size()) {
855 return "Invalid solution hint: vars and values do not have the same size.";
857 for (
const int ref :
hint.vars()) {
858 if (!VariableReferenceIsValid(
model, ref)) {
859 return absl::StrCat(
"Invalid variable reference in solution hint: ", ref);
864 absl::flat_hash_set<int> indices;
865 for (
const int var :
hint.vars()) {
867 if (!insert.second) {
869 "The solution hint contains duplicate variables like the variable "
876 for (
const int64_t
value :
hint.values()) {
877 if (
value == std::numeric_limits<int64_t>::min() ||
878 value == std::numeric_limits<int64_t>::max()) {
879 return "The solution hint cannot contains the INT_MIN or INT_MAX values.";
889 absl::Span<const int> vars,
890 absl::Span<const int64_t> coeffs, int64_t offset) {
891 if (offset == std::numeric_limits<int64_t>::min())
return true;
892 int64_t sum_min = -std::abs(offset);
893 int64_t sum_max = +std::abs(offset);
894 for (
int i = 0;
i < vars.size(); ++
i) {
895 const int ref = vars[
i];
897 const int64_t min_domain = var_proto.domain(0);
898 const int64_t max_domain = var_proto.domain(var_proto.domain_size() - 1);
899 if (coeffs[
i] == std::numeric_limits<int64_t>::min())
return true;
901 const int64_t prod1 =
CapProd(min_domain, coeff);
902 const int64_t prod2 =
CapProd(max_domain, coeff);
907 sum_min =
CapAdd(sum_min, std::min(int64_t{0}, std::min(prod1, prod2)));
908 sum_max =
CapAdd(sum_max, std::max(int64_t{0}, std::max(prod1, prod2)));
909 for (
const int64_t v : {prod1, prod2, sum_min, sum_max}) {
920 if (sum_min < -std::numeric_limits<int64_t>::max() / 2)
return true;
921 if (sum_max > std::numeric_limits<int64_t>::max() / 2)
return true;
926 int64_t int128_overflow = 0;
927 for (
int v = 0; v <
model.variables_size(); ++v) {
930 const auto& domain =
model.variables(v).domain();
931 const int64_t
min = domain[0];
932 const int64_t
max = domain[domain.size() - 1];
934 int128_overflow, std::max({std::abs(
min), std::abs(
max),
max -
min}));
940 if (int128_overflow == std::numeric_limits<int64_t>::max()) {
941 return "The sum of all variable domains do not fit on an int64_t. This is "
942 "needed to prevent overflows.";
947 std::vector<int> constraints_using_intervals;
949 for (
int c = 0; c <
model.constraints_size(); ++c) {
954 bool support_enforcement =
false;
957 const ConstraintProto&
ct =
model.constraints(c);
958 switch (
ct.constraint_case()) {
959 case ConstraintProto::ConstraintCase::kBoolOr:
960 support_enforcement =
true;
962 case ConstraintProto::ConstraintCase::kBoolAnd:
963 support_enforcement =
true;
965 case ConstraintProto::ConstraintCase::kLinear:
966 support_enforcement =
true;
969 case ConstraintProto::ConstraintCase::kLinMax: {
971 ValidateLinearExpression(
model,
ct.lin_max().target()));
972 for (
const LinearExpressionProto& expr :
ct.lin_max().exprs()) {
977 case ConstraintProto::ConstraintCase::kIntProd:
980 case ConstraintProto::ConstraintCase::kIntDiv:
983 case ConstraintProto::ConstraintCase::kIntMod:
986 case ConstraintProto::ConstraintCase::kInverse:
987 if (
ct.inverse().f_direct().size() !=
ct.inverse().f_inverse().size()) {
988 return absl::StrCat(
"Non-matching fields size in inverse: ",
992 case ConstraintProto::ConstraintCase::kAllDiff:
993 for (
const LinearExpressionProto& expr :
ct.all_diff().exprs()) {
997 case ConstraintProto::ConstraintCase::kElement:
1000 case ConstraintProto::ConstraintCase::kTable:
1002 support_enforcement =
true;
1004 case ConstraintProto::ConstraintCase::kAutomaton:
1007 case ConstraintProto::ConstraintCase::kCircuit:
1009 ValidateGraphInput(
false,
ct.circuit()));
1011 case ConstraintProto::ConstraintCase::kRoutes:
1014 case ConstraintProto::ConstraintCase::kInterval:
1016 support_enforcement =
true;
1018 case ConstraintProto::ConstraintCase::kCumulative:
1019 constraints_using_intervals.push_back(c);
1021 case ConstraintProto::ConstraintCase::kNoOverlap:
1022 constraints_using_intervals.push_back(c);
1024 case ConstraintProto::ConstraintCase::kNoOverlap2D:
1025 constraints_using_intervals.push_back(c);
1027 case ConstraintProto::ConstraintCase::kReservoir:
1030 case ConstraintProto::ConstraintCase::kDummyConstraint:
1031 return "The dummy constraint should never appear in a model.";
1039 if (!support_enforcement && !
ct.enforcement_literal().empty()) {
1040 for (
const int ref :
ct.enforcement_literal()) {
1043 if (domain.Size() != 1) {
1044 return absl::StrCat(
1045 "Enforcement literal not supported in constraint: ",
1053 for (
const int c : constraints_using_intervals) {
1055 ValidateIntervalsUsedInConstraint(after_presolve,
model, c));
1057 const ConstraintProto&
ct =
model.constraints(c);
1058 switch (
ct.constraint_case()) {
1059 case ConstraintProto::ConstraintCase::kCumulative:
1062 case ConstraintProto::ConstraintCase::kNoOverlap:
1064 case ConstraintProto::ConstraintCase::kNoOverlap2D:
1068 LOG(DFATAL) <<
"Shouldn't be here";
1072 if (
model.has_objective() &&
model.has_floating_point_objective()) {
1073 return "A model cannot have both an objective and a floating point "
1076 if (
model.has_objective()) {
1079 if (
model.objective().integer_scaling_factor() != 0 ||
1080 model.objective().integer_before_offset() != 0 ||
1081 model.objective().integer_after_offset() != 0) {
1083 if (
model.objective().domain().empty()) {
1084 return absl::StrCat(
1085 "Objective integer scaling or offset is set without an objective "
1091 bool overflow =
false;
1092 for (
const int64_t v :
model.objective().domain()) {
1093 int64_t t =
CapAdd(v,
model.objective().integer_before_offset());
1098 t =
CapProd(t,
model.objective().integer_scaling_factor());
1103 t =
CapAdd(t,
model.objective().integer_after_offset());
1110 return absl::StrCat(
1111 "Internal fields related to the postsolve of the integer objective "
1112 "are causing a potential integer overflow: ",
1119 for (
const int ref :
model.assumptions()) {
1120 if (!LiteralReferenceIsValid(
model, ref)) {
1121 return absl::StrCat(
"Invalid literal reference ", ref,
1122 " in the 'assumptions' field.");
1129 const CpModelProto&
model) {
1131 if (
model.has_floating_point_objective()) {
1133 ValidateFloatingPointObjective(params.mip_max_valid_magnitude(),
model,
1134 model.floating_point_objective()));
1139#undef RETURN_IF_NOT_EMPTY
1147class ConstraintChecker {
1149 explicit ConstraintChecker(absl::Span<const int64_t> variable_values)
1150 : variable_values_(variable_values.begin(), variable_values.
end()) {}
1152 bool LiteralIsTrue(
int l)
const {
1153 if (l >= 0)
return variable_values_[l] != 0;
1154 return variable_values_[-l - 1] == 0;
1157 bool LiteralIsFalse(
int l)
const {
return !LiteralIsTrue(l); }
1160 if (
var >= 0)
return variable_values_[
var];
1161 return -variable_values_[-
var - 1];
1164 bool ConstraintIsEnforced(
const ConstraintProto&
ct) {
1165 for (
const int lit :
ct.enforcement_literal()) {
1166 if (LiteralIsFalse(
lit))
return false;
1171 bool BoolOrConstraintIsFeasible(
const ConstraintProto&
ct) {
1172 for (
const int lit :
ct.bool_or().literals()) {
1173 if (LiteralIsTrue(
lit))
return true;
1178 bool BoolAndConstraintIsFeasible(
const ConstraintProto&
ct) {
1179 for (
const int lit :
ct.bool_and().literals()) {
1180 if (LiteralIsFalse(
lit))
return false;
1185 bool AtMostOneConstraintIsFeasible(
const ConstraintProto&
ct) {
1186 int num_true_literals = 0;
1187 for (
const int lit :
ct.at_most_one().literals()) {
1188 if (LiteralIsTrue(
lit)) ++num_true_literals;
1190 return num_true_literals <= 1;
1193 bool ExactlyOneConstraintIsFeasible(
const ConstraintProto&
ct) {
1194 int num_true_literals = 0;
1195 for (
const int lit :
ct.exactly_one().literals()) {
1196 if (LiteralIsTrue(
lit)) ++num_true_literals;
1198 return num_true_literals == 1;
1201 bool BoolXorConstraintIsFeasible(
const ConstraintProto&
ct) {
1203 for (
const int lit :
ct.bool_xor().literals()) {
1204 sum ^= LiteralIsTrue(
lit) ? 1 : 0;
1209 bool LinearConstraintIsFeasible(
const ConstraintProto&
ct) {
1211 const int num_variables =
ct.linear().coeffs_size();
1212 const int*
const vars =
ct.linear().vars().data();
1213 const int64_t*
const coeffs =
ct.linear().coeffs().data();
1214 for (
int i = 0;
i < num_variables; ++
i) {
1217 sum += variable_values_[vars[
i]] * coeffs[
i];
1221 VLOG(1) <<
"Activity: " << sum;
1226 int64_t LinearExpressionValue(
const LinearExpressionProto& expr)
const {
1227 int64_t sum = expr.offset();
1228 const int num_variables = expr.vars_size();
1229 for (
int i = 0;
i < num_variables; ++
i) {
1230 sum +=
Value(expr.vars(
i)) * expr.coeffs(
i);
1235 bool LinMaxConstraintIsFeasible(
const ConstraintProto&
ct) {
1236 const int64_t
max = LinearExpressionValue(
ct.lin_max().target());
1237 int64_t actual_max = std::numeric_limits<int64_t>::min();
1238 for (
int i = 0;
i <
ct.lin_max().exprs_size(); ++
i) {
1239 const int64_t expr_value = LinearExpressionValue(
ct.lin_max().exprs(
i));
1240 actual_max = std::max(actual_max, expr_value);
1242 return max == actual_max;
1245 bool IntProdConstraintIsFeasible(
const ConstraintProto&
ct) {
1246 const int64_t prod = LinearExpressionValue(
ct.int_prod().target());
1247 int64_t actual_prod = 1;
1248 for (
const LinearExpressionProto& expr :
ct.int_prod().exprs()) {
1249 actual_prod =
CapProd(actual_prod, LinearExpressionValue(expr));
1251 return prod == actual_prod;
1254 bool IntDivConstraintIsFeasible(
const ConstraintProto&
ct) {
1255 return LinearExpressionValue(
ct.int_div().target()) ==
1256 LinearExpressionValue(
ct.int_div().exprs(0)) /
1257 LinearExpressionValue(
ct.int_div().exprs(1));
1260 bool IntModConstraintIsFeasible(
const ConstraintProto&
ct) {
1261 return LinearExpressionValue(
ct.int_mod().target()) ==
1262 LinearExpressionValue(
ct.int_mod().exprs(0)) %
1263 LinearExpressionValue(
ct.int_mod().exprs(1));
1266 bool AllDiffConstraintIsFeasible(
const ConstraintProto&
ct) {
1267 absl::flat_hash_set<int64_t> values;
1268 for (
const LinearExpressionProto& expr :
ct.all_diff().exprs()) {
1269 const int64_t
value = LinearExpressionValue(expr);
1270 const auto [it, inserted] = values.insert(
value);
1271 if (!inserted)
return false;
1276 int64_t IntervalStart(
const IntervalConstraintProto&
interval)
const {
1277 return LinearExpressionValue(
interval.start());
1280 int64_t IntervalSize(
const IntervalConstraintProto&
interval)
const {
1281 return LinearExpressionValue(
interval.size());
1284 int64_t IntervalEnd(
const IntervalConstraintProto&
interval)
const {
1285 return LinearExpressionValue(
interval.end());
1288 bool IntervalConstraintIsFeasible(
const ConstraintProto&
ct) {
1289 const int64_t
size = IntervalSize(
ct.interval());
1290 if (
size < 0)
return false;
1291 return IntervalStart(
ct.interval()) +
size == IntervalEnd(
ct.interval());
1294 bool NoOverlapConstraintIsFeasible(
const CpModelProto&
model,
1295 const ConstraintProto&
ct) {
1296 std::vector<std::pair<int64_t, int64_t>> start_durations_pairs;
1297 for (
const int i :
ct.no_overlap().intervals()) {
1298 const ConstraintProto& interval_constraint =
model.constraints(
i);
1299 if (ConstraintIsEnforced(interval_constraint)) {
1300 const IntervalConstraintProto&
interval =
1301 interval_constraint.interval();
1302 start_durations_pairs.push_back(
1306 std::sort(start_durations_pairs.begin(), start_durations_pairs.end());
1307 int64_t previous_end = std::numeric_limits<int64_t>::min();
1308 for (
const auto& pair : start_durations_pairs) {
1309 if (pair.first < previous_end)
return false;
1310 previous_end = pair.first + pair.second;
1315 bool IntervalsAreDisjoint(
const IntervalConstraintProto& interval1,
1316 const IntervalConstraintProto& interval2) {
1317 return IntervalEnd(interval1) <= IntervalStart(interval2) ||
1318 IntervalEnd(interval2) <= IntervalStart(interval1);
1321 bool IntervalIsEmpty(
const IntervalConstraintProto&
interval) {
1325 bool NoOverlap2DConstraintIsFeasible(
const CpModelProto&
model,
1326 const ConstraintProto&
ct) {
1327 const auto& arg =
ct.no_overlap_2d();
1330 std::vector<std::pair<
const IntervalConstraintProto*
const,
1331 const IntervalConstraintProto*
const>>
1332 enforced_intervals_xy;
1334 const int num_intervals = arg.x_intervals_size();
1335 CHECK_EQ(arg.y_intervals_size(), num_intervals);
1336 for (
int i = 0;
i < num_intervals; ++
i) {
1337 const ConstraintProto&
x =
model.constraints(arg.x_intervals(
i));
1338 const ConstraintProto&
y =
model.constraints(arg.y_intervals(
i));
1339 if (ConstraintIsEnforced(
x) && ConstraintIsEnforced(
y)) {
1340 enforced_intervals_xy.push_back({&
x.interval(), &
y.interval()});
1344 const int num_enforced_intervals = enforced_intervals_xy.size();
1345 for (
int i = 0;
i < num_enforced_intervals; ++
i) {
1346 for (
int j =
i + 1; j < num_enforced_intervals; ++j) {
1347 const auto& xi = *enforced_intervals_xy[
i].first;
1348 const auto& yi = *enforced_intervals_xy[
i].second;
1349 const auto& xj = *enforced_intervals_xy[j].first;
1350 const auto& yj = *enforced_intervals_xy[j].second;
1351 if (!IntervalsAreDisjoint(xi, xj) && !IntervalsAreDisjoint(yi, yj)) {
1352 VLOG(1) <<
"Interval " <<
i <<
"(x=[" << IntervalStart(xi) <<
", "
1353 << IntervalEnd(xi) <<
"], y=[" << IntervalStart(yi) <<
", "
1354 << IntervalEnd(yi) <<
"]) and " << j <<
"(x=["
1355 << IntervalStart(xj) <<
", " << IntervalEnd(xj) <<
"], y=["
1356 << IntervalStart(yj) <<
", " << IntervalEnd(yj)
1357 <<
"]) are not disjoint.";
1365 bool CumulativeConstraintIsFeasible(
const CpModelProto&
model,
1366 const ConstraintProto&
ct) {
1367 const int64_t capacity = LinearExpressionValue(
ct.cumulative().capacity());
1368 const int num_intervals =
ct.cumulative().intervals_size();
1369 std::vector<std::pair<int64_t, int64_t>> events;
1370 for (
int i = 0;
i < num_intervals; ++
i) {
1371 const ConstraintProto& interval_constraint =
1372 model.constraints(
ct.cumulative().intervals(
i));
1373 if (!ConstraintIsEnforced(interval_constraint))
continue;
1374 const int64_t
start = IntervalStart(interval_constraint.interval());
1375 const int64_t duration = IntervalSize(interval_constraint.interval());
1376 const int64_t
demand = LinearExpressionValue(
ct.cumulative().demands(
i));
1377 if (duration == 0 ||
demand == 0)
continue;
1381 if (events.empty())
return true;
1383 std::sort(events.begin(), events.end());
1386 int64_t current_load = 0;
1387 for (
const auto& [
time,
delta] : events) {
1388 current_load +=
delta;
1389 if (current_load > capacity) {
1390 VLOG(1) <<
"Cumulative constraint: load: " << current_load
1391 <<
" capacity: " << capacity <<
" time: " <<
time;
1395 DCHECK_EQ(current_load, 0);
1399 bool ElementConstraintIsFeasible(
const ConstraintProto&
ct) {
1400 if (
ct.element().vars().empty())
return false;
1406 bool TableConstraintIsFeasible(
const ConstraintProto&
ct) {
1407 const int size =
ct.table().vars_size();
1408 if (
size == 0)
return true;
1409 for (
int row_start = 0; row_start <
ct.table().values_size();
1410 row_start +=
size) {
1412 while (
Value(
ct.table().vars(
i)) ==
ct.table().values(row_start +
i)) {
1414 if (
i ==
size)
return !
ct.table().negated();
1417 return ct.table().negated();
1420 bool AutomatonConstraintIsFeasible(
const ConstraintProto&
ct) {
1422 absl::flat_hash_map<std::pair<int64_t, int64_t>, int64_t> transition_map;
1423 const int num_transitions =
ct.automaton().transition_tail().size();
1424 for (
int i = 0;
i < num_transitions; ++
i) {
1425 transition_map[{
ct.automaton().transition_tail(
i),
1426 ct.automaton().transition_label(
i)}] =
1427 ct.automaton().transition_head(
i);
1431 int64_t current_state =
ct.automaton().starting_state();
1432 const int num_steps =
ct.automaton().vars_size();
1433 for (
int i = 0;
i < num_steps; ++
i) {
1434 const std::pair<int64_t, int64_t> key = {current_state,
1436 if (!transition_map.contains(key)) {
1439 current_state = transition_map[key];
1443 for (
const int64_t
final :
ct.automaton().final_states()) {
1444 if (current_state ==
final)
return true;
1449 bool CircuitConstraintIsFeasible(
const ConstraintProto&
ct) {
1452 const int num_arcs =
ct.circuit().tails_size();
1453 absl::flat_hash_set<int>
nodes;
1454 absl::flat_hash_map<int, int> nexts;
1455 for (
int i = 0;
i < num_arcs; ++
i) {
1456 const int tail =
ct.circuit().tails(
i);
1457 const int head =
ct.circuit().heads(
i);
1460 if (LiteralIsFalse(
ct.circuit().literals(
i)))
continue;
1461 if (nexts.contains(
tail)) {
1462 VLOG(1) <<
"Node with two outgoing arcs";
1471 for (
const int node :
nodes) {
1472 if (!nexts.contains(node)) {
1473 VLOG(1) <<
"Node with no next: " << node;
1476 if (nexts[node] == node)
continue;
1480 if (cycle_size == 0)
return true;
1484 absl::flat_hash_set<int> visited;
1485 int current = in_cycle;
1486 int num_visited = 0;
1487 while (!visited.contains(current)) {
1489 visited.insert(current);
1490 current = nexts[current];
1492 if (current != in_cycle) {
1493 VLOG(1) <<
"Rho shape";
1496 if (num_visited != cycle_size) {
1497 VLOG(1) <<
"More than one cycle";
1499 return num_visited == cycle_size;
1502 bool RoutesConstraintIsFeasible(
const ConstraintProto&
ct) {
1503 const int num_arcs =
ct.routes().tails_size();
1504 int num_used_arcs = 0;
1505 int num_self_arcs = 0;
1507 std::vector<int> tail_to_head;
1508 std::vector<int> depot_nexts;
1509 for (
int i = 0;
i < num_arcs; ++
i) {
1510 const int tail =
ct.routes().tails(
i);
1511 const int head =
ct.routes().heads(
i);
1512 num_nodes = std::max(num_nodes, 1 +
tail);
1513 num_nodes = std::max(num_nodes, 1 +
head);
1514 tail_to_head.resize(num_nodes, -1);
1515 if (LiteralIsTrue(
ct.routes().literals(
i))) {
1517 if (
tail == 0)
return false;
1523 depot_nexts.push_back(
head);
1525 if (tail_to_head[
tail] != -1)
return false;
1532 if (num_nodes == 0)
return true;
1536 for (
int start : depot_nexts) {
1538 while (
start != 0) {
1539 if (tail_to_head[
start] == -1)
return false;
1545 if (count != num_used_arcs) {
1546 VLOG(1) <<
"count: " << count <<
" != num_used_arcs:" << num_used_arcs;
1554 if (count - depot_nexts.size() + 1 + num_self_arcs != num_nodes) {
1555 VLOG(1) <<
"Not all nodes are covered!";
1562 bool InverseConstraintIsFeasible(
const ConstraintProto&
ct) {
1563 const int num_variables =
ct.inverse().f_direct_size();
1564 if (num_variables !=
ct.inverse().f_inverse_size())
return false;
1566 for (
int i = 0;
i < num_variables;
i++) {
1567 const int fi =
Value(
ct.inverse().f_direct(
i));
1568 if (fi < 0 || num_variables <= fi)
return false;
1569 if (
i !=
Value(
ct.inverse().f_inverse(fi)))
return false;
1574 bool ReservoirConstraintIsFeasible(
const ConstraintProto&
ct) {
1575 const int num_variables =
ct.reservoir().time_exprs_size();
1576 const int64_t min_level =
ct.reservoir().min_level();
1577 const int64_t max_level =
ct.reservoir().max_level();
1578 absl::btree_map<int64_t, int64_t> deltas;
1579 const bool has_active_variables =
ct.reservoir().active_literals_size() > 0;
1580 for (
int i = 0;
i < num_variables;
i++) {
1581 const int64_t
time = LinearExpressionValue(
ct.reservoir().time_exprs(
i));
1582 if (!has_active_variables ||
1583 Value(
ct.reservoir().active_literals(
i)) == 1) {
1584 const int64_t level =
1585 LinearExpressionValue(
ct.reservoir().level_changes(
i));
1586 deltas[
time] += level;
1589 int64_t current_level = 0;
1590 for (
const auto&
delta : deltas) {
1591 current_level +=
delta.second;
1592 if (current_level < min_level || current_level > max_level) {
1593 VLOG(1) <<
"Reservoir level " << current_level
1594 <<
" is out of bounds at time" <<
delta.first;
1602 const ConstraintProto&
ct) {
1604 if (!ConstraintIsEnforced(
ct))
return true;
1606 const ConstraintProto::ConstraintCase type =
ct.constraint_case();
1608 case ConstraintProto::ConstraintCase::kBoolOr:
1609 return BoolOrConstraintIsFeasible(
ct);
1610 case ConstraintProto::ConstraintCase::kBoolAnd:
1611 return BoolAndConstraintIsFeasible(
ct);
1612 case ConstraintProto::ConstraintCase::kAtMostOne:
1613 return AtMostOneConstraintIsFeasible(
ct);
1614 case ConstraintProto::ConstraintCase::kExactlyOne:
1615 return ExactlyOneConstraintIsFeasible(
ct);
1616 case ConstraintProto::ConstraintCase::kBoolXor:
1617 return BoolXorConstraintIsFeasible(
ct);
1618 case ConstraintProto::ConstraintCase::kLinear:
1619 return LinearConstraintIsFeasible(
ct);
1620 case ConstraintProto::ConstraintCase::kIntProd:
1621 return IntProdConstraintIsFeasible(
ct);
1622 case ConstraintProto::ConstraintCase::kIntDiv:
1623 return IntDivConstraintIsFeasible(
ct);
1624 case ConstraintProto::ConstraintCase::kIntMod:
1625 return IntModConstraintIsFeasible(
ct);
1626 case ConstraintProto::ConstraintCase::kLinMax:
1627 return LinMaxConstraintIsFeasible(
ct);
1628 case ConstraintProto::ConstraintCase::kAllDiff:
1629 return AllDiffConstraintIsFeasible(
ct);
1630 case ConstraintProto::ConstraintCase::kInterval:
1631 if (!IntervalConstraintIsFeasible(
ct)) {
1632 if (
ct.interval().has_start()) {
1638 LOG(ERROR) <<
"Warning, an interval constraint was likely used "
1639 "without a corresponding linear constraint linking "
1640 "its start, size and end.";
1645 case ConstraintProto::ConstraintCase::kNoOverlap:
1646 return NoOverlapConstraintIsFeasible(
model,
ct);
1647 case ConstraintProto::ConstraintCase::kNoOverlap2D:
1648 return NoOverlap2DConstraintIsFeasible(
model,
ct);
1649 case ConstraintProto::ConstraintCase::kCumulative:
1650 return CumulativeConstraintIsFeasible(
model,
ct);
1651 case ConstraintProto::ConstraintCase::kElement:
1652 return ElementConstraintIsFeasible(
ct);
1653 case ConstraintProto::ConstraintCase::kTable:
1654 return TableConstraintIsFeasible(
ct);
1655 case ConstraintProto::ConstraintCase::kAutomaton:
1656 return AutomatonConstraintIsFeasible(
ct);
1657 case ConstraintProto::ConstraintCase::kCircuit:
1658 return CircuitConstraintIsFeasible(
ct);
1659 case ConstraintProto::ConstraintCase::kRoutes:
1660 return RoutesConstraintIsFeasible(
ct);
1661 case ConstraintProto::ConstraintCase::kInverse:
1662 return InverseConstraintIsFeasible(
ct);
1663 case ConstraintProto::ConstraintCase::kReservoir:
1664 return ReservoirConstraintIsFeasible(
ct);
1665 case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1675 const std::vector<int64_t> variable_values_;
1681 const ConstraintProto& constraint,
1682 absl::Span<const int64_t> variable_values) {
1683 ConstraintChecker checker(variable_values);
1684 return checker.ConstraintIsFeasible(
model, constraint);
1688 absl::Span<const int64_t> variable_values,
1689 const CpModelProto* mapping_proto,
1690 const std::vector<int>* postsolve_mapping) {
1691 if (variable_values.size() !=
model.variables_size()) {
1692 VLOG(1) <<
"Wrong number of variables (" << variable_values.size()
1693 <<
") in the solution vector. It should be "
1694 <<
model.variables_size() <<
".";
1699 for (
int i = 0;
i <
model.variables_size(); ++
i) {
1701 VLOG(1) <<
"Variable #" <<
i <<
" has value " << variable_values[
i]
1702 <<
" which do not fall in its domain: "
1708 CHECK_EQ(variable_values.size(),
model.variables_size());
1709 ConstraintChecker checker(variable_values);
1711 for (
int c = 0;
c <
model.constraints_size(); ++
c) {
1712 const ConstraintProto&
ct =
model.constraints(c);
1713 if (checker.ConstraintIsFeasible(
model,
ct))
continue;
1716 VLOG(1) <<
"Failing constraint #" <<
c <<
" : "
1718 if (mapping_proto !=
nullptr && postsolve_mapping !=
nullptr) {
1719 std::vector<int> reverse_map(mapping_proto->variables().size(), -1);
1720 for (
int var = 0;
var < postsolve_mapping->size(); ++
var) {
1721 reverse_map[(*postsolve_mapping)[
var]] =
var;
1724 VLOG(1) <<
"var: " <<
var <<
" mapped_to: " << reverse_map[
var]
1725 <<
" value: " << variable_values[
var] <<
" initial_domain: "
1727 <<
" postsolved_domain: "
1732 VLOG(1) <<
"var: " <<
var <<
" value: " << variable_values[
var];
1743 if (
model.has_objective()) {
1744 int64_t inner_objective = 0;
1745 const int num_variables =
model.objective().coeffs_size();
1746 for (
int i = 0;
i < num_variables; ++
i) {
1747 inner_objective += checker.Value(
model.objective().vars(
i)) *
1748 model.objective().coeffs(
i);
1750 if (!
model.objective().domain().empty()) {
1752 VLOG(1) <<
"Objective value " << inner_objective <<
" not in domain! "
1757 double factor =
model.objective().scaling_factor();
1758 if (factor == 0.0) factor = 1.0;
1759 const double scaled_objective =
1761 (
static_cast<double>(inner_objective) +
model.objective().offset());
1762 VLOG(2) <<
"Checker inner objective = " << inner_objective;
1763 VLOG(2) <<
"Checker scaled objective = " << scaled_objective;
#define RETURN_IF_NOT_EMPTY(statement)
If the string returned by "statement" is not empty, returns it.
CpModelProto proto
The output proto.
std::optional< ModelSolveParameters::SolutionHint > hint
absl::Status ValidateLinearExpression(const LinearExpressionProto &expression, const IdNameBiMap &variable_universe)
std::string ValidateInputCpModel(const SatParameters ¶ms, const CpModelProto &model)
bool RefIsPositive(int ref)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
bool SolutionIsFeasible(const CpModelProto &model, absl::Span< const int64_t > variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
bool ConstraintIsFeasible(const CpModelProto &model, const ConstraintProto &constraint, absl::Span< const int64_t > variable_values)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
Returns the sorted list of interval used by a constraint.
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
This checks that the variable is fixed.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
absl::string_view ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)
In SWIG mode, we don't want anything besides these top-level includes.
bool AtMinOrMaxInt64(int64_t x)
Checks if x is equal to the min or the max value of an int64_t.
int64_t CapAdd(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
int64_t CapProd(int64_t x, int64_t y)
int64_t CapAbs(int64_t v)
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
std::string ProtobufDebugString(const P &message)
static int input(yyscan_t yyscanner)
std::optional< int64_t > end