24#include "absl/algorithm/container.h"
25#include "absl/base/log_severity.h"
26#include "absl/container/btree_map.h"
27#include "absl/log/check.h"
28#include "absl/log/log.h"
29#include "absl/strings/str_cat.h"
41enum class EncodingLinear1Type {
48const int kNumEncodingLinear1Types = 5;
50template <
typename Sink>
53 case EncodingLinear1Type::kVarEqValue:
54 sink.Append(
"kVarEqValue");
56 case EncodingLinear1Type::kVarNeValue:
57 sink.Append(
"kVarNeValue");
59 case EncodingLinear1Type::kVarGeValue:
60 sink.Append(
"kVarGeValue");
62 case EncodingLinear1Type::kVarLeValue:
63 sink.Append(
"kVarLeValue");
65 case EncodingLinear1Type::kVarInDomain:
66 sink.Append(
"kVarInDomain");
71enum class EncodingLinear1Status {
78struct EncodingLinear1 {
79 EncodingLinear1Type type;
80 int64_t value = std::numeric_limits<int64_t>::min();
82 int enforcement_literal;
86 return absl::StrCat(
"EncodingLinear1(type: ", type,
", value: ", value,
87 ", rhs: ", rhs.ToString(),
88 ", enforcement_literal: ", enforcement_literal,
89 ", constraint_index: ", constraint_index,
")");
93std::pair<std::optional<EncodingLinear1>, EncodingLinear1Status> ProcessLinear1(
94 PresolveContext* context,
int constraint_index,
const Domain& var_domain) {
96 context->working_model->constraints(constraint_index);
101 lin.enforcement_literal = ct.enforcement_literal(0);
102 lin.constraint_index = constraint_index;
105 if (!context->SetLiteralToFalse(lin.enforcement_literal)) {
106 return {std::nullopt, EncodingLinear1Status::kUnsat};
108 return {std::nullopt, EncodingLinear1Status::kIgnore};
110 }
else if (rhs.IsFixed()) {
111 if (!var_domain.
Contains(rhs.FixedValue())) {
112 if (!context->SetLiteralToFalse(lin.enforcement_literal)) {
113 return {std::nullopt, EncodingLinear1Status::kUnsat};
115 return {std::nullopt, EncodingLinear1Status::kIgnore};
117 lin.type = EncodingLinear1Type::kVarEqValue;
118 lin.value = rhs.FixedValue();
121 const Domain complement = var_domain.IntersectionWith(rhs.Complement());
122 if (complement.IsEmpty()) {
123 return {std::nullopt, EncodingLinear1Status::kIgnore};
124 }
else if (complement.IsFixed()) {
125 DCHECK(var_domain.
Contains(complement.FixedValue()));
126 lin.type = EncodingLinear1Type::kVarNeValue;
127 lin.value = complement.FixedValue();
128 }
else if (rhs.
Min() > complement.
Max()) {
129 lin.type = EncodingLinear1Type::kVarGeValue;
130 lin.value = rhs.
Min();
131 }
else if (rhs.
Max() < complement.
Min()) {
132 lin.type = EncodingLinear1Type::kVarLeValue;
133 lin.value = rhs.
Max();
135 lin.type = EncodingLinear1Type::kVarInDomain;
139 return {lin, EncodingLinear1Status::kOk};
142template <
typename Sink>
144 sink.Append(lin.ToString());
150 : var_(var), var_domain_(context->DomainOf(var)), context_(context) {}
154 encoded_values_.push_back(value);
155 referenced_encoded_values_.push_back(value);
160 encoded_values_.push_back(value);
164 bool push_down_when_unconstrained,
bool has_le_ge_linear1) {
167 if (!has_le_ge_linear1) {
169 encoded_values_ = referenced_encoded_values_;
177 if (encoded_values_.size() < var_domain_.Size()) {
180 const int64_t escape_value =
181 push_down_when_unconstrained ? residual.
Min() : residual.
Max();
183 encoded_values_.push_back(escape_value);
185 if (!has_le_ge_linear1) {
186 unique_escape_value_ = escape_value;
190 is_fully_encoded_ = encoded_values_.size() == var_domain_.Size();
196 return encoded_values_;
201 return encoded_values_.empty();
206 return is_fully_encoded_;
211 return unique_escape_value_;
215 encoded_values_.clear();
216 referenced_encoded_values_.clear();
217 encoded_values_.reserve(var_domain_.Size());
218 for (
const int64_t v : var_domain_.Values()) {
219 encoded_values_.push_back(v);
222 is_fully_encoded_ =
true;
227 for (
const int64_t value : encoded_values_) {
228 encoding_[value] = context_->GetOrCreateVarValueEncoding(var_, value);
234 const auto it = encoding_.find(value);
235 DCHECK(it != encoding_.end());
247 var_domain_(context->DomainOf(var)),
249 solution_crush_(solution_crush) {}
252 if (!tmp_le_to_literals_[value].insert(literal).second)
return;
253 DCHECK_LT(value, var_domain_.Max());
254 const int64_t next_value = var_domain_.ValueAtOrAfter(value + 1);
255 if (tmp_ge_to_literals_[next_value].contains(
NegatedRef(literal))) {
256 const auto [it, inserted] = encoded_le_literal_.insert({value, literal});
258 VLOG(2) <<
"Duplicate var_le_value literal: " << literal
259 <<
" for value: " << value <<
" previous: " << it->second;
265 if (!tmp_ge_to_literals_[value].insert(literal).second)
return;
266 DCHECK_GT(value, var_domain_.Min());
267 const int64_t previous_value = var_domain_.ValueAtOrBefore(value - 1);
268 if (tmp_le_to_literals_[previous_value].contains(
NegatedRef(literal))) {
269 const auto [it, inserted] =
270 encoded_le_literal_.insert({previous_value,
NegatedRef(literal)});
272 VLOG(2) <<
"Duplicate var_le_value literal: " <<
NegatedRef(literal)
273 <<
" for value: " << previous_value
274 <<
" previous: " << it->second;
304 CollectAllOrderEncodingValues();
305 if (encoded_le_literal_.empty())
return;
309 for (
const auto& [value, literal] : encoded_le_literal_) {
310 CHECK(values.
encoding().contains(value));
311 CHECK(values.
encoding().contains(var_domain_.ValueAtOrAfter(value + 1)))
312 <<
"Cannot find " << var_domain_.ValueAtOrAfter(value + 1)
313 <<
" for var <= " << value;
317 const int64_t max_le_value = encoded_le_literal_.rbegin()->first;
318 const int64_t max_ge_value = var_domain_.ValueAtOrAfter(max_le_value + 1);
324 for (
const auto [value, eq_literal] : values.
encoding()) {
325 const int ne_literal =
NegatedRef(eq_literal);
328 if (not_le !=
nullptr) {
335 const auto it_le = encoded_le_literal_.find(value);
336 if (it_le != encoded_le_literal_.end()) {
339 DCHECK(le !=
nullptr);
341 if (value < max_le_value) {
342 le = context_->working_model->add_constraints();
348 if (not_le !=
nullptr) {
351 not_le = context_->AddEnforcedConstraint({
le_literal});
355 if (value > var_domain_.Min()) {
357 encoded_le_literal_.find(var_domain_.ValueAtOrBefore(value - 1));
358 if (it_ge != encoded_le_literal_.end()) {
364 ge = context_->AddEnforcedConstraint({
ge_literal});
366 DCHECK(not_ge !=
nullptr);
368 if (value != max_ge_value) {
369 not_ge = context_->working_model->add_constraints();
379 if (not_ge !=
nullptr) {
386 DCHECK_GT(value, var_domain_.Min());
388 encoded_le_literal_.find(var_domain_.ValueAtOrBefore(value - 1));
389 DCHECK(it != encoded_le_literal_.end());
394 const auto it = encoded_le_literal_.find(value);
395 DCHECK(it != encoded_le_literal_.end());
400 return encoded_le_literal_.size();
403void OrderEncoding::CollectAllOrderEncodingValues() {
404 for (
const auto& [value, lits] : tmp_le_to_literals_) {
405 const auto it = encoded_le_literal_.find(value);
406 if (it != encoded_le_literal_.end())
continue;
413 for (
const auto& [value, lits] : tmp_ge_to_literals_) {
415 const auto it = encoded_le_literal_.find(previous_value);
416 if (it != encoded_le_literal_.end())
continue;
427 std::vector<std::vector<EncodingLinear1>>& linear_ones_by_type,
428 std::vector<int>& constraint_indices,
bool& var_in_objective,
429 bool& var_has_positive_objective_coefficient) {
474 constraint_indices.clear();
475 var_in_objective =
false;
476 var_has_positive_objective_coefficient =
false;
479 const int64_t obj_coeff = context->
ObjectiveMap().at(var);
480 var_in_objective =
true;
481 var_has_positive_objective_coefficient = obj_coeff > 0;
485 constraint_indices.push_back(c);
488 const bool push_down_when_unconstrained =
489 !var_in_objective || var_has_positive_objective_coefficient;
492 absl::c_sort(constraint_indices);
493 for (
const int c : constraint_indices) {
501 "TODO variables: linear1 with multiple enforcement literals");
505 const auto& [opt_lin, status] = ProcessLinear1(context, c, var_domain);
506 if (!opt_lin.has_value()) {
507 if (status == EncodingLinear1Status::kUnsat)
return false;
508 if (status == EncodingLinear1Status::kIgnore)
continue;
509 if (status == EncodingLinear1Status::kAbort) {
511 "TODO variables: only used in complex linear1");
516 const EncodingLinear1& lin = opt_lin.value();
517 VLOG(3) <<
"ProcessVariableOnlyUsedInEncoding(): var(" << var
518 <<
") domain: " << var_domain <<
" linear1: " << lin;
521 case EncodingLinear1Type::kVarEqValue:
523 if (push_down_when_unconstrained) {
524 if (lin.value < var_domain.
Max()) {
529 if (lin.value > var_domain.
Min()) {
535 case EncodingLinear1Type::kVarNeValue:
537 if (push_down_when_unconstrained) {
538 if (lin.value < var_domain.
Max()) {
543 if (lin.value > var_domain.
Min()) {
549 case EncodingLinear1Type::kVarGeValue: {
556 case EncodingLinear1Type::kVarLeValue: {
563 case EncodingLinear1Type::kVarInDomain: {
569 linear_ones_by_type[
static_cast<int>(lin.type)].push_back(lin);
571 const bool has_le_ge_linear1 =
572 !linear_ones_by_type[
static_cast<int>(EncodingLinear1Type::kVarGeValue)]
574 !linear_ones_by_type[
static_cast<int>(EncodingLinear1Type::kVarLeValue)]
577 push_down_when_unconstrained, has_le_ge_linear1);
584 std::vector<std::vector<EncodingLinear1>> linear_ones_by_type(
585 kNumEncodingLinear1Types);
588 bool var_in_objective =
false;
589 bool var_has_positive_objective_coefficient =
false;
590 std::vector<int> constraint_indices;
592 var, context, values, order, linear_ones_by_type, constraint_indices,
593 var_in_objective, var_has_positive_objective_coefficient)) {
599 linear_ones_by_type[
static_cast<int>(EncodingLinear1Type::kVarEqValue)];
601 linear_ones_by_type[
static_cast<int>(EncodingLinear1Type::kVarNeValue)];
603 linear_ones_by_type[
static_cast<int>(EncodingLinear1Type::kVarGeValue)];
605 linear_ones_by_type[
static_cast<int>(EncodingLinear1Type::kVarLeValue)];
606 const auto& lin_domain =
607 linear_ones_by_type[
static_cast<int>(EncodingLinear1Type::kVarInDomain)];
612 const bool full_encoding_is_not_too_expensive =
614 const bool full_encoding_is_needed =
615 (!lin_domain.empty() ||
618 full_encoding_is_needed) {
619 VLOG(3) <<
"Forcing full encoding of var: " << var;
623 if (values.
empty()) {
630 VLOG(2) <<
"ProcessVariableOnlyUsedInEncoding(): var(" << var
631 <<
"): " << var_domain <<
", size: " << var_domain.
Size()
634 <<
", #var_eq_value: " << lin_eq.size()
635 <<
", #var_ne_value: " << lin_ne.size()
636 <<
", #var_ge_value: " << lin_ge.size()
637 <<
", #var_le_value: " << lin_le.size()
638 <<
", #var_in_domain: " << lin_domain.size()
639 <<
", var_in_objective: " << var_in_objective
640 <<
", var_has_positive_objective_coefficient: "
641 << var_has_positive_objective_coefficient;
642 if (full_encoding_is_needed &&
644 var_domain.
Size() * lin_domain.size() > 2500)) {
646 <<
", full_encoding_is_not_too_expensive: "
647 << full_encoding_is_not_too_expensive
648 <<
", full_encoding_is_needed: " << full_encoding_is_needed;
649 if (var_in_objective) {
651 "TODO variables: only used in objective and in complex encodings");
654 "TODO variables: only used in large complex encodings");
681 const bool push_down_when_unconstrained =
682 !var_in_objective || var_has_positive_objective_coefficient;
690 for (
const EncodingLinear1& info_eq : lin_eq) {
692 values.
literal(info_eq.value));
695 for (
const EncodingLinear1& info_ne : lin_ne) {
700 for (
const EncodingLinear1& info_ge : lin_ge) {
705 for (
const EncodingLinear1& info_le : lin_le) {
710 for (
const EncodingLinear1& info_in : lin_domain) {
713 for (
const int64_t v : info_in.rhs.Values()) {
719 const Domain implied_complement =
721 for (
const int64_t v : implied_complement.
Values()) {
727 const auto add_incompatibility = [context](
const EncodingLinear1& info_i,
728 const EncodingLinear1& info_j) {
729 const int e_i = info_i.enforcement_literal;
730 const int e_j = info_j.enforcement_literal;
737 "variables: add at_most_one between incompatible complex encodings");
740 for (
int i = 0;
i < lin_domain.size(); ++
i) {
741 const EncodingLinear1& info_i = lin_domain[
i];
742 DCHECK_EQ(info_i.type, EncodingLinear1Type::kVarInDomain);
745 for (
const EncodingLinear1& info_j : lin_ge) {
746 DCHECK_EQ(info_j.type, EncodingLinear1Type::kVarGeValue);
747 if (info_i.rhs.Max() < info_j.value) {
748 add_incompatibility(info_i, info_j);
753 for (
const EncodingLinear1& info_j : lin_le) {
754 DCHECK_EQ(info_j.type, EncodingLinear1Type::kVarLeValue);
755 if (info_i.rhs.Min() > info_j.value) {
756 add_incompatibility(info_i, info_j);
761 for (
int j =
i + 1; j < lin_domain.size(); ++j) {
762 const EncodingLinear1& info_j = lin_domain[j];
763 DCHECK_EQ(info_j.type, EncodingLinear1Type::kVarInDomain);
764 if (!info_i.rhs.OverlapsWith(info_j.rhs)) {
765 add_incompatibility(info_i, info_j);
773 if (var_in_objective) {
777 const int64_t base_value = var_has_positive_objective_coefficient
787 int64_t accumulated = std::abs(base_value);
789 accumulated =
CapAdd(accumulated, std::abs(
CapSub(value, base_value)));
790 if (accumulated == std::numeric_limits<int64_t>::max()) {
791 VLOG(2) <<
"Abort - overflow when converting linear1 to clauses";
793 "TODO variables: overflow when converting linear1 to clauses");
802 const int64_t coeff_in_equality = -1;
805 int64_t rhs_value = -base_value;
806 for (
const auto& [value, literal] : values.
encoding()) {
807 const int64_t coeff = value - base_value;
808 if (coeff == 0)
continue;
824 "TODO variables: cannot substitute encoded variable in objective");
828 "variables: only used in objective and in encodings");
830 if ((!lin_eq.empty() || !lin_ne.empty()) && lin_domain.empty()) {
832 "variables: only used in value and order encodings");
833 }
else if (!lin_domain.empty()) {
840 VLOG(2) <<
"Reduce domain size: " << var_domain.
Size() <<
" to "
843 context->
UpdateRuleStats(
"variables: reduce domain to encoded values");
850 std::vector<int> to_clear;
852 if (c >= 0) to_clear.push_back(c);
854 absl::c_sort(to_clear);
855 for (
const int c : to_clear) {
864 for (
const auto& [value, literal] : values.
encoding()) {
872 const int64_t var_min = var_domain.
Min();
877 int64_t offset = var_min;
878 for (
const auto& [value, literal] : values.
encoding()) {
879 const int64_t coeff = value - var_min;
880 if (coeff == 0)
continue;
static Domain FromValues(std::vector< int64_t > values)
int64_t ValueAtOrAfter(int64_t input) const
Domain IntersectionWith(const Domain &domain) const
DomainIteratorBeginEnd Values() const &
Domain InverseMultiplicationBy(int64_t coeff) const
int64_t ValueAtOrBefore(int64_t input) const
void add_literals(::int32_t value)
ConstraintCase constraint_case() const
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_bool_and()
::int32_t enforcement_literal(int index) const
const ::operations_research::sat::LinearConstraintProto & linear() const
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_exactly_one()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_bool_or()
void add_enforcement_literal(::int32_t value)
ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL mutable_constraints(int index)
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL add_constraints()
::int32_t vars(int index) const
void add_vars(::int32_t value)
void add_domain(::int64_t value)
void add_coeffs(::int64_t value)
int ge_literal(int64_t value) const
OrderEncoding(int var, PresolveContext *context, SolutionCrush &solution_crush)
void CreateAllOrderEncodingLiterals(const ValueEncoding &values)
int le_literal(int64_t value) const
void InsertLeLiteral(int64_t value, int literal)
void InsertGeLiteral(int64_t value, int literal)
int num_encoded_values() const
bool ObjectiveDomainIsConstraining() const
CpModelProto * working_model
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality)
void AddImplication(int a, int b)
int NewBoolVar(absl::string_view source)
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
void UpdateConstraintVariableUsage(int c)
ConstraintProto * AddEnforcedConstraint(absl::Span< const int > enforcement_literals)
const absl::flat_hash_set< int > & VarToConstraints(int var) const
bool IsMostlyFullyEncoded(int ref) const
void MarkVariableAsRemoved(int ref)
void UpdateNewConstraintsVariableUsage()
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
const Domain & DomainOf(int var) const
void UpdateRuleStats(std::string_view name, int num_times=1)
bool ModelIsUnsat() const
void MaybeSetLiteralToOrderEncoding(int literal, int var, int64_t value, bool is_le)
void SetOrUpdateVarToDomainWithOptionalEscapeValue(int var, const Domain &reduced_var_domain, std::optional< int64_t > unique_escape_value, bool push_down_when_not_in_domain, const absl::btree_map< int64_t, int > &encoding)
void AddOptionalValueToEncode(int64_t value)
void CreateAllValueEncodingLiterals()
const absl::btree_map< int64_t, int > & encoding() const
bool is_fully_encoded() const
const std::vector< int64_t > & encoded_values() const
int literal(int64_t value) const
std::optional< int64_t > unique_escape_value() const
ValueEncoding(int var, PresolveContext *context)
void AddReferencedValueToEncode(int64_t value)
void CanonicalizeEncodedValuesAndAddEscapeValue(bool push_down_when_unconstrained, bool has_le_ge_linear1)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
bool RefIsPositive(int ref)
bool ProcessEncodingConstraints(int var, PresolveContext *context, ValueEncoding &values, OrderEncoding &order, std::vector< std::vector< EncodingLinear1 > > &linear_ones_by_type, std::vector< int > &constraint_indices, bool &var_in_objective, bool &var_has_positive_objective_coefficient)
void TryToReplaceVariableByItsEncoding(int var, PresolveContext *context, SolutionCrush &solution_crush)
constexpr int kObjectiveConstraint
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void AbslStringify(Sink &sink, EdgePosition e)
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
bool Contains(int64_t value) const