862 lines
30 KiB
C++
862 lines
30 KiB
C++
// Copyright 2010-2018 Google LLC
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "ortools/sat/presolve_context.h"
|
|
|
|
#include "ortools/base/map_util.h"
|
|
#include "ortools/base/mathutil.h"
|
|
#include "ortools/port/proto_utils.h"
|
|
|
|
namespace operations_research {
|
|
namespace sat {
|
|
|
|
void PresolveContext::ClearStats() { stats_by_rule_name.clear(); }
|
|
|
|
int PresolveContext::NewIntVar(const Domain& domain) {
|
|
IntegerVariableProto* const var = working_model->add_variables();
|
|
FillDomainInProto(domain, var);
|
|
InitializeNewDomains();
|
|
return working_model->variables_size() - 1;
|
|
}
|
|
|
|
int PresolveContext::NewBoolVar() { return NewIntVar(Domain(0, 1)); }
|
|
|
|
int PresolveContext::GetOrCreateConstantVar(int64 cst) {
|
|
if (!gtl::ContainsKey(constant_to_ref, cst)) {
|
|
constant_to_ref[cst] = working_model->variables_size();
|
|
IntegerVariableProto* const var_proto = working_model->add_variables();
|
|
var_proto->add_domain(cst);
|
|
var_proto->add_domain(cst);
|
|
InitializeNewDomains();
|
|
}
|
|
return constant_to_ref[cst];
|
|
}
|
|
|
|
// a => b.
|
|
void PresolveContext::AddImplication(int a, int b) {
|
|
ConstraintProto* const ct = working_model->add_constraints();
|
|
ct->add_enforcement_literal(a);
|
|
ct->mutable_bool_and()->add_literals(b);
|
|
}
|
|
|
|
// b => x in [lb, ub].
|
|
void PresolveContext::AddImplyInDomain(int b, int x, const Domain& domain) {
|
|
ConstraintProto* const imply = working_model->add_constraints();
|
|
|
|
// Doing it like this seems to use slightly less memory.
|
|
// TODO(user): Find the best way to create such small proto.
|
|
imply->mutable_enforcement_literal()->Resize(1, b);
|
|
LinearConstraintProto* mutable_linear = imply->mutable_linear();
|
|
mutable_linear->mutable_vars()->Resize(1, x);
|
|
mutable_linear->mutable_coeffs()->Resize(1, 1);
|
|
FillDomainInProto(domain, mutable_linear);
|
|
}
|
|
|
|
bool PresolveContext::DomainIsEmpty(int ref) const {
|
|
return domains[PositiveRef(ref)].IsEmpty();
|
|
}
|
|
|
|
bool PresolveContext::IsFixed(int ref) const {
|
|
DCHECK(!DomainIsEmpty(ref));
|
|
return domains[PositiveRef(ref)].IsFixed();
|
|
}
|
|
|
|
bool PresolveContext::CanBeUsedAsLiteral(int ref) const {
|
|
const int var = PositiveRef(ref);
|
|
return domains[var].Min() >= 0 && domains[var].Max() <= 1;
|
|
}
|
|
|
|
bool PresolveContext::LiteralIsTrue(int lit) const {
|
|
DCHECK(CanBeUsedAsLiteral(lit));
|
|
if (RefIsPositive(lit)) {
|
|
return domains[lit].Min() == 1;
|
|
} else {
|
|
return domains[PositiveRef(lit)].Max() == 0;
|
|
}
|
|
}
|
|
|
|
bool PresolveContext::LiteralIsFalse(int lit) const {
|
|
DCHECK(CanBeUsedAsLiteral(lit));
|
|
if (RefIsPositive(lit)) {
|
|
return domains[lit].Max() == 0;
|
|
} else {
|
|
return domains[PositiveRef(lit)].Min() == 1;
|
|
}
|
|
}
|
|
|
|
int64 PresolveContext::MinOf(int ref) const {
|
|
DCHECK(!DomainIsEmpty(ref));
|
|
return RefIsPositive(ref) ? domains[PositiveRef(ref)].Min()
|
|
: -domains[PositiveRef(ref)].Max();
|
|
}
|
|
|
|
int64 PresolveContext::MaxOf(int ref) const {
|
|
DCHECK(!DomainIsEmpty(ref));
|
|
return RefIsPositive(ref) ? domains[PositiveRef(ref)].Max()
|
|
: -domains[PositiveRef(ref)].Min();
|
|
}
|
|
|
|
bool PresolveContext::VariableIsNotRepresentativeOfEquivalenceClass(
|
|
int ref) const {
|
|
if (affine_relations_.ClassSize(PositiveRef(ref)) == 1) return true;
|
|
return PositiveRef(GetAffineRelation(ref).representative) != PositiveRef(ref);
|
|
}
|
|
|
|
// TODO(user): In some case, we could still remove var when it has some variable
|
|
// in affine relation with it, but we need to be careful that none are used.
|
|
bool PresolveContext::VariableIsUniqueAndRemovable(int ref) const {
|
|
if (!ConstraintVariableGraphIsUpToDate()) return false;
|
|
return var_to_constraints[PositiveRef(ref)].size() == 1 &&
|
|
VariableIsNotRepresentativeOfEquivalenceClass(ref) &&
|
|
!keep_all_feasible_solutions;
|
|
}
|
|
|
|
bool PresolveContext::VariableWithCostIsUniqueAndRemovable(int ref) const {
|
|
if (!ConstraintVariableGraphIsUpToDate()) return false;
|
|
const int var = PositiveRef(ref);
|
|
return !keep_all_feasible_solutions && var_to_constraints[var].contains(-1) &&
|
|
var_to_constraints[var].size() == 2 &&
|
|
VariableIsNotRepresentativeOfEquivalenceClass(ref);
|
|
}
|
|
|
|
Domain PresolveContext::DomainOf(int ref) const {
|
|
Domain result;
|
|
if (RefIsPositive(ref)) {
|
|
result = domains[ref];
|
|
} else {
|
|
result = domains[PositiveRef(ref)].Negation();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool PresolveContext::DomainContains(int ref, int64 value) const {
|
|
if (!RefIsPositive(ref)) {
|
|
return domains[PositiveRef(ref)].Contains(-value);
|
|
}
|
|
return domains[ref].Contains(value);
|
|
}
|
|
|
|
ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith(
|
|
int ref, const Domain& domain, bool* domain_modified) {
|
|
DCHECK(!DomainIsEmpty(ref));
|
|
const int var = PositiveRef(ref);
|
|
|
|
if (RefIsPositive(ref)) {
|
|
if (domains[var].IsIncludedIn(domain)) {
|
|
return true;
|
|
}
|
|
domains[var] = domains[var].IntersectionWith(domain);
|
|
} else {
|
|
const Domain temp = domain.Negation();
|
|
if (domains[var].IsIncludedIn(temp)) {
|
|
return true;
|
|
}
|
|
domains[var] = domains[var].IntersectionWith(temp);
|
|
}
|
|
|
|
if (domain_modified != nullptr) {
|
|
*domain_modified = true;
|
|
}
|
|
modified_domains.Set(var);
|
|
if (domains[var].IsEmpty()) {
|
|
is_unsat = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToFalse(int lit) {
|
|
const int var = PositiveRef(lit);
|
|
const int64 value = RefIsPositive(lit) ? 0 : 1;
|
|
return IntersectDomainWith(var, Domain(value));
|
|
}
|
|
|
|
ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToTrue(int lit) {
|
|
return SetLiteralToFalse(NegatedRef(lit));
|
|
}
|
|
|
|
void PresolveContext::UpdateRuleStats(const std::string& name) {
|
|
if (enable_stats) {
|
|
VLOG(1) << num_presolve_operations << " : " << name;
|
|
stats_by_rule_name[name]++;
|
|
}
|
|
num_presolve_operations++;
|
|
}
|
|
|
|
void PresolveContext::AddVariableUsage(int c) {
|
|
const ConstraintProto& ct = working_model->constraints(c);
|
|
constraint_to_vars[c] = UsedVariables(ct);
|
|
constraint_to_intervals[c] = UsedIntervals(ct);
|
|
for (const int v : constraint_to_vars[c]) var_to_constraints[v].insert(c);
|
|
for (const int i : constraint_to_intervals[c]) interval_usage[i]++;
|
|
}
|
|
|
|
void PresolveContext::UpdateConstraintVariableUsage(int c) {
|
|
DCHECK_EQ(constraint_to_vars.size(), working_model->constraints_size());
|
|
const ConstraintProto& ct = working_model->constraints(c);
|
|
|
|
// We don't optimize the interval usage as this is not super frequent.
|
|
for (const int i : constraint_to_intervals[c]) interval_usage[i]--;
|
|
constraint_to_intervals[c] = UsedIntervals(ct);
|
|
for (const int i : constraint_to_intervals[c]) interval_usage[i]++;
|
|
|
|
// For the variables, we avoid an erase() followed by an insert() for the
|
|
// variables that didn't change.
|
|
tmp_new_usage_ = UsedVariables(ct);
|
|
const std::vector<int>& old_usage = constraint_to_vars[c];
|
|
const int old_size = old_usage.size();
|
|
int i = 0;
|
|
for (const int var : tmp_new_usage_) {
|
|
while (i < old_size && old_usage[i] < var) {
|
|
var_to_constraints[old_usage[i]].erase(c);
|
|
++i;
|
|
}
|
|
if (i < old_size && old_usage[i] == var) {
|
|
++i;
|
|
} else {
|
|
var_to_constraints[var].insert(c);
|
|
}
|
|
}
|
|
for (; i < old_size; ++i) var_to_constraints[old_usage[i]].erase(c);
|
|
constraint_to_vars[c] = tmp_new_usage_;
|
|
}
|
|
|
|
bool PresolveContext::ConstraintVariableGraphIsUpToDate() const {
|
|
return constraint_to_vars.size() == working_model->constraints_size();
|
|
}
|
|
|
|
void PresolveContext::UpdateNewConstraintsVariableUsage() {
|
|
const int old_size = constraint_to_vars.size();
|
|
const int new_size = working_model->constraints_size();
|
|
CHECK_LE(old_size, new_size);
|
|
constraint_to_vars.resize(new_size);
|
|
constraint_to_intervals.resize(new_size);
|
|
interval_usage.resize(new_size);
|
|
for (int c = old_size; c < new_size; ++c) {
|
|
AddVariableUsage(c);
|
|
}
|
|
}
|
|
|
|
bool PresolveContext::ConstraintVariableUsageIsConsistent() {
|
|
if (is_unsat) return true; // We do not care in this case.
|
|
if (constraint_to_vars.size() != working_model->constraints_size()) {
|
|
LOG(INFO) << "Wrong constraint_to_vars size!";
|
|
return false;
|
|
}
|
|
for (int c = 0; c < constraint_to_vars.size(); ++c) {
|
|
if (constraint_to_vars[c] != UsedVariables(working_model->constraints(c))) {
|
|
LOG(INFO) << "Wrong variables usage for constraint: \n"
|
|
<< ProtobufDebugString(working_model->constraints(c));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// If a Boolean variable (one with domain [0, 1]) appear in this affine
|
|
// equivalence class, then we want its representative to be Boolean. Note that
|
|
// this is always possible because a Boolean variable can never be equal to a
|
|
// multiple of another if std::abs(coeff) is greater than 1 and if it is not
|
|
// fixed to zero. This is important because it allows to simply use the same
|
|
// representative for any referenced literals.
|
|
bool PresolveContext::AddRelation(int x, int y, int c, int o,
|
|
AffineRelation* repo) {
|
|
const int rep_x = repo->Get(x).representative;
|
|
const int rep_y = repo->Get(y).representative;
|
|
const bool allow_rep_x = CanBeUsedAsLiteral(rep_x);
|
|
const bool allow_rep_y = CanBeUsedAsLiteral(rep_y);
|
|
if (allow_rep_x || allow_rep_y) {
|
|
return repo->TryAdd(x, y, c, o, allow_rep_x, allow_rep_y);
|
|
} else {
|
|
// If none are boolean, we do not care about which is used as
|
|
// representative.
|
|
return repo->TryAdd(x, y, c, o);
|
|
}
|
|
}
|
|
|
|
void PresolveContext::ExploitFixedDomain(int var) {
|
|
CHECK(RefIsPositive(var));
|
|
CHECK(IsFixed(var));
|
|
const int min = MinOf(var);
|
|
if (gtl::ContainsKey(constant_to_ref, min)) {
|
|
const int representative = constant_to_ref[min];
|
|
if (representative != var) {
|
|
AddRelation(var, representative, 1, 0, &affine_relations_);
|
|
AddRelation(var, representative, 1, 0, &var_equiv_relations_);
|
|
}
|
|
} else {
|
|
constant_to_ref[min] = var;
|
|
}
|
|
}
|
|
|
|
void PresolveContext::StoreAffineRelation(const ConstraintProto& ct, int ref_x,
|
|
int ref_y, int64 coeff,
|
|
int64 offset) {
|
|
if (is_unsat) return;
|
|
if (IsFixed(ref_x) || IsFixed(ref_y)) return;
|
|
|
|
const int x = PositiveRef(ref_x);
|
|
const int y = PositiveRef(ref_y);
|
|
const int64 c = RefIsPositive(ref_x) == RefIsPositive(ref_y) ? coeff : -coeff;
|
|
const int64 o = RefIsPositive(ref_x) ? offset : -offset;
|
|
|
|
// TODO(user): can we force the rep and remove GetAffineRelation()?
|
|
bool added = AddRelation(x, y, c, o, &affine_relations_);
|
|
if ((c == 1 || c == -1) && o == 0) {
|
|
added |= AddRelation(x, y, c, o, &var_equiv_relations_);
|
|
}
|
|
if (added) {
|
|
// The domain didn't change, but this notification allows to re-process any
|
|
// constraint containing these variables. Note that we do not need to
|
|
// retrigger a propagation of the constraint containing a variable whose
|
|
// representative didn't change.
|
|
if (GetAffineRelation(x).representative != x) modified_domains.Set(x);
|
|
if (GetAffineRelation(y).representative != y) modified_domains.Set(y);
|
|
affine_constraints.insert(&ct);
|
|
}
|
|
}
|
|
|
|
void PresolveContext::StoreBooleanEqualityRelation(int ref_a, int ref_b) {
|
|
if (ref_a == ref_b) return;
|
|
if (ref_a == NegatedRef(ref_b)) {
|
|
is_unsat = true;
|
|
return;
|
|
}
|
|
|
|
// For now, we do need to add the relation ref_a == ref_b so we have a
|
|
// proper variable usage count and propagation between ref_a and ref_b.
|
|
//
|
|
// TODO(user): This looks unclean. We should probably handle the affine
|
|
// relation together without the need of keep all the constraints that
|
|
// define them around.
|
|
ConstraintProto* ct = working_model->add_constraints();
|
|
auto* arg = ct->mutable_linear();
|
|
arg->add_vars(PositiveRef(ref_a));
|
|
arg->add_vars(PositiveRef(ref_b));
|
|
if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) {
|
|
// a = b
|
|
arg->add_coeffs(1);
|
|
arg->add_coeffs(-1);
|
|
arg->add_domain(0);
|
|
arg->add_domain(0);
|
|
StoreAffineRelation(*ct, PositiveRef(ref_a), PositiveRef(ref_b), 1,
|
|
/*offset=*/0);
|
|
} else {
|
|
// a = 1 - b
|
|
arg->add_coeffs(1);
|
|
arg->add_coeffs(1);
|
|
arg->add_domain(1);
|
|
arg->add_domain(1);
|
|
StoreAffineRelation(*ct, PositiveRef(ref_a), PositiveRef(ref_b), -1,
|
|
/*offset=*/1);
|
|
}
|
|
UpdateNewConstraintsVariableUsage();
|
|
}
|
|
|
|
bool PresolveContext::StoreAbsRelation(int target_ref, int ref) {
|
|
const auto insert_status =
|
|
abs_relations.insert(std::make_pair(target_ref, PositiveRef(ref)));
|
|
return insert_status.second;
|
|
}
|
|
|
|
int PresolveContext::GetLiteralRepresentative(int ref) const {
|
|
const AffineRelation::Relation r = GetAffineRelation(PositiveRef(ref));
|
|
|
|
// We made sure that the affine representative can always be used as a
|
|
// literal. However, if some variable are fixed, we might not have only
|
|
// (coeff=1 offset=0) or (coeff=-1 offset=1) and we might have something like
|
|
// (coeff=8 offset=0) which is only valid for both variable at zero...
|
|
//
|
|
// What is sure is that depending on the value, only one mapping can be valid
|
|
// because r.coeff can never be zero.
|
|
DCHECK(CanBeUsedAsLiteral(ref));
|
|
DCHECK(CanBeUsedAsLiteral(r.representative));
|
|
const bool positive_possible = (r.offset == 0 || r.coeff + r.offset == 1);
|
|
const bool negative_possible = (r.offset == 1 || r.coeff + r.offset == 0);
|
|
DCHECK_NE(positive_possible, negative_possible);
|
|
if (RefIsPositive(ref)) {
|
|
return positive_possible ? r.representative : NegatedRef(r.representative);
|
|
} else {
|
|
return positive_possible ? NegatedRef(r.representative) : r.representative;
|
|
}
|
|
}
|
|
|
|
int PresolveContext::GetVariableRepresentative(int ref) const {
|
|
const AffineRelation::Relation r = var_equiv_relations_.Get(PositiveRef(ref));
|
|
CHECK_EQ(std::abs(r.coeff), 1);
|
|
CHECK_EQ(r.offset, 0);
|
|
return RefIsPositive(ref) == (r.coeff == 1) ? r.representative
|
|
: NegatedRef(r.representative);
|
|
}
|
|
|
|
// This makes sure that the affine relation only uses one of the
|
|
// representative from the var_equiv_relations_.
|
|
AffineRelation::Relation PresolveContext::GetAffineRelation(int ref) const {
|
|
AffineRelation::Relation r = affine_relations_.Get(PositiveRef(ref));
|
|
AffineRelation::Relation o = var_equiv_relations_.Get(r.representative);
|
|
r.representative = o.representative;
|
|
if (o.coeff == -1) r.coeff = -r.coeff;
|
|
if (!RefIsPositive(ref)) {
|
|
r.coeff *= -1;
|
|
r.offset *= -1;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Create the internal structure for any new variables in working_model.
|
|
void PresolveContext::InitializeNewDomains() {
|
|
for (int i = domains.size(); i < working_model->variables_size(); ++i) {
|
|
domains.emplace_back(ReadDomainFromProto(working_model->variables(i)));
|
|
if (domains.back().IsEmpty()) {
|
|
is_unsat = true;
|
|
return;
|
|
}
|
|
if (IsFixed(i)) ExploitFixedDomain(i);
|
|
}
|
|
modified_domains.Resize(domains.size());
|
|
var_to_constraints.resize(domains.size());
|
|
var_to_ub_only_constraints.resize(domains.size());
|
|
var_to_lb_only_constraints.resize(domains.size());
|
|
}
|
|
|
|
void PresolveContext::InsertVarValueEncoding(int literal, int ref,
|
|
int64 value) {
|
|
const int var = PositiveRef(ref);
|
|
const int64 var_value = RefIsPositive(ref) ? value : -value;
|
|
const std::pair<std::pair<int, int64>, int> key =
|
|
std::make_pair(std::make_pair(var, var_value), literal);
|
|
const auto& insert = encoding.insert(key);
|
|
if (insert.second) {
|
|
if (DomainOf(var).Size() == 2) {
|
|
// Encode the other literal.
|
|
const int64 var_min = MinOf(var);
|
|
const int64 var_max = MaxOf(var);
|
|
const int64 other_value = value == var_min ? var_max : var_min;
|
|
const std::pair<int, int64> other_key{var, other_value};
|
|
auto other_it = encoding.find(other_key);
|
|
if (other_it != encoding.end()) {
|
|
// Other value in the domain was already encoded.
|
|
const int previous_other_literal = other_it->second;
|
|
if (previous_other_literal != NegatedRef(literal)) {
|
|
StoreBooleanEqualityRelation(literal,
|
|
NegatedRef(previous_other_literal));
|
|
}
|
|
} else {
|
|
encoding[other_key] = NegatedRef(literal);
|
|
// Add affine relation.
|
|
ConstraintProto* const ct = working_model->add_constraints();
|
|
LinearConstraintProto* const lin = ct->mutable_linear();
|
|
lin->add_vars(var);
|
|
lin->add_coeffs(1);
|
|
lin->add_vars(PositiveRef(literal));
|
|
if (RefIsPositive(literal) == (var_value == var_max)) {
|
|
lin->add_coeffs(var_min - var_max);
|
|
lin->add_domain(var_min);
|
|
lin->add_domain(var_min);
|
|
StoreAffineRelation(*ct, var, PositiveRef(literal), var_max - var_min,
|
|
var_min);
|
|
} else {
|
|
lin->add_coeffs(var_max - var_min);
|
|
lin->add_domain(var_max);
|
|
lin->add_domain(var_max);
|
|
StoreAffineRelation(*ct, var, PositiveRef(literal), var_min - var_max,
|
|
var_max);
|
|
}
|
|
UpdateNewConstraintsVariableUsage();
|
|
}
|
|
} else {
|
|
VLOG(2) << "Insert lit(" << literal << ") <=> var(" << var
|
|
<< ") == " << value;
|
|
const std::pair<int, int64> key{var, var_value};
|
|
eq_half_encoding[key].insert(literal);
|
|
AddImplyInDomain(literal, var, Domain(var_value));
|
|
neq_half_encoding[key].insert(NegatedRef(literal));
|
|
AddImplyInDomain(NegatedRef(literal), var,
|
|
Domain(var_value).Complement());
|
|
}
|
|
} else {
|
|
const int previous_literal = insert.first->second;
|
|
if (literal != previous_literal) {
|
|
StoreBooleanEqualityRelation(literal, previous_literal);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PresolveContext::InsertHalfVarValueEncoding(int literal, int var,
|
|
int64 value, bool imply_eq) {
|
|
CHECK(RefIsPositive(var));
|
|
|
|
// Creates the linking maps on demand.
|
|
const std::pair<int, int64> key{var, value};
|
|
auto& direct_map = imply_eq ? eq_half_encoding[key] : neq_half_encoding[key];
|
|
auto& other_map = imply_eq ? neq_half_encoding[key] : eq_half_encoding[key];
|
|
|
|
// Insert the reference literal in the half encoding map.
|
|
const auto& new_info = direct_map.insert(literal);
|
|
if (new_info.second) {
|
|
VLOG(2) << "Collect lit(" << literal << ") implies var(" << var
|
|
<< (imply_eq ? ") == " : ") != ") << value;
|
|
UpdateRuleStats("variables: detect half reified value encoding");
|
|
|
|
if (other_map.contains(NegatedRef(literal))) {
|
|
const int imply_eq_literal = imply_eq ? literal : NegatedRef(literal);
|
|
|
|
const auto insert_encoding_status =
|
|
encoding.insert(std::make_pair(key, imply_eq_literal));
|
|
if (insert_encoding_status.second) {
|
|
VLOG(2) << "Detect and store lit(" << imply_eq_literal << ") <=> var("
|
|
<< var << ") == " << value;
|
|
UpdateRuleStats("variables: detect fully reified value encoding");
|
|
} else if (imply_eq_literal != insert_encoding_status.first->second) {
|
|
const int previous_imply_eq_literal =
|
|
insert_encoding_status.first->second;
|
|
VLOG(2) << "Detect duplicate encoding lit(" << imply_eq_literal
|
|
<< ") == lit(" << previous_imply_eq_literal << ") <=> var("
|
|
<< var << ") == " << value;
|
|
StoreBooleanEqualityRelation(imply_eq_literal,
|
|
previous_imply_eq_literal);
|
|
|
|
UpdateRuleStats(
|
|
"variables: merge equivalent var value encoding literals");
|
|
}
|
|
}
|
|
}
|
|
|
|
return new_info.second;
|
|
}
|
|
|
|
bool PresolveContext::StoreLiteralImpliesVarEqValue(int literal, int var,
|
|
int64 value) {
|
|
return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/true);
|
|
}
|
|
|
|
bool PresolveContext::StoreLiteralImpliesVarNEqValue(int literal, int var,
|
|
int64 value) {
|
|
return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/false);
|
|
}
|
|
|
|
bool PresolveContext::HasVarValueEncoding(int ref, int64 value, int* literal) {
|
|
const int var = PositiveRef(ref);
|
|
const int64 var_value = RefIsPositive(ref) ? value : -value;
|
|
const std::pair<int, int64> key{var, var_value};
|
|
const auto& it = encoding.find(key);
|
|
if (it != encoding.end()) {
|
|
if (literal != nullptr) {
|
|
*literal = it->second;
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int PresolveContext::GetOrCreateVarValueEncoding(int ref, int64 value) {
|
|
// TODO(user,user): use affine relation here.
|
|
const int var = PositiveRef(ref);
|
|
const int64 var_value = RefIsPositive(ref) ? value : -value;
|
|
|
|
// Returns the false literal if the value is not in the domain.
|
|
if (!domains[var].Contains(var_value)) {
|
|
return GetOrCreateConstantVar(0);
|
|
}
|
|
|
|
// Returns the associated literal if already present.
|
|
const std::pair<int, int64> key{var, var_value};
|
|
auto it = encoding.find(key);
|
|
if (it != encoding.end()) {
|
|
return GetLiteralRepresentative(it->second);
|
|
}
|
|
|
|
// Special case for fixed domains.
|
|
if (domains[var].Size() == 1) {
|
|
const int true_literal = GetOrCreateConstantVar(1);
|
|
encoding[key] = true_literal;
|
|
return true_literal;
|
|
}
|
|
|
|
// Special case for domains of size 2.
|
|
const int64 var_min = MinOf(var);
|
|
const int64 var_max = MaxOf(var);
|
|
if (domains[var].Size() == 2) {
|
|
// Checks if the other value is already encoded.
|
|
const int64 other_value = var_value == var_min ? var_max : var_min;
|
|
const std::pair<int, int64> other_key{var, other_value};
|
|
auto other_it = encoding.find(other_key);
|
|
if (other_it != encoding.end()) {
|
|
// Update the encoding map. The domain could have been reduced to size
|
|
// two after the creation of the first literal.
|
|
const int other_literal =
|
|
GetLiteralRepresentative(NegatedRef(other_it->second));
|
|
encoding[key] = other_literal;
|
|
return other_literal;
|
|
}
|
|
|
|
if (var_min == 0 && var_max == 1) {
|
|
const int representative = GetLiteralRepresentative(var);
|
|
encoding[{var, 1}] = representative;
|
|
encoding[{var, 0}] = NegatedRef(representative);
|
|
return value == 1 ? representative : NegatedRef(representative);
|
|
} else {
|
|
const int literal = NewBoolVar();
|
|
InsertVarValueEncoding(literal, var, var_max);
|
|
const int representative = GetLiteralRepresentative(literal);
|
|
return var_value == var_max ? representative : NegatedRef(representative);
|
|
}
|
|
}
|
|
|
|
const int literal = NewBoolVar();
|
|
InsertVarValueEncoding(literal, var, var_value);
|
|
return GetLiteralRepresentative(literal);
|
|
}
|
|
|
|
void PresolveContext::ReadObjectiveFromProto() {
|
|
const CpObjectiveProto& obj = working_model->objective();
|
|
|
|
objective_offset = obj.offset();
|
|
objective_scaling_factor = obj.scaling_factor();
|
|
if (objective_scaling_factor == 0.0) {
|
|
objective_scaling_factor = 1.0;
|
|
}
|
|
if (!obj.domain().empty()) {
|
|
// We might relax this in CanonicalizeObjective() when we will compute
|
|
// the possible objective domain from the domains of the variables.
|
|
objective_domain_is_constraining = true;
|
|
objective_domain = ReadDomainFromProto(obj);
|
|
} else {
|
|
objective_domain_is_constraining = false;
|
|
objective_domain = Domain::AllValues();
|
|
}
|
|
|
|
objective_map.clear();
|
|
for (int i = 0; i < obj.vars_size(); ++i) {
|
|
const int ref = obj.vars(i);
|
|
int64 coeff = obj.coeffs(i);
|
|
if (!RefIsPositive(ref)) coeff = -coeff;
|
|
int var = PositiveRef(ref);
|
|
|
|
objective_map[var] += coeff;
|
|
if (objective_map[var] == 0) {
|
|
objective_map.erase(var);
|
|
var_to_constraints[var].erase(-1);
|
|
} else {
|
|
var_to_constraints[var].insert(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PresolveContext::CanonicalizeObjective() {
|
|
int64 offset_change = 0;
|
|
|
|
// We replace each entry by its affine representative.
|
|
// Note that the non-deterministic loop is fine, but because we iterate
|
|
// one the map while modifying it, it is safer to do a copy rather than to
|
|
// try to handle that in one pass.
|
|
tmp_entries.clear();
|
|
for (const auto& entry : objective_map) {
|
|
tmp_entries.push_back(entry);
|
|
}
|
|
|
|
// TODO(user): This is a bit duplicated with the presolve linear code.
|
|
// We also do not propagate back any domain restriction from the objective to
|
|
// the variables if any.
|
|
for (const auto& entry : tmp_entries) {
|
|
const int var = entry.first;
|
|
const auto it = objective_map.find(var);
|
|
if (it == objective_map.end()) continue;
|
|
const int64 coeff = it->second;
|
|
|
|
// If a variable only appear in objective, we can fix it!
|
|
if (!keep_all_feasible_solutions && !objective_domain_is_constraining &&
|
|
ConstraintVariableGraphIsUpToDate() &&
|
|
VariableIsNotRepresentativeOfEquivalenceClass(var) &&
|
|
var_to_constraints[var].size() == 1 &&
|
|
var_to_constraints[var].contains(-1)) {
|
|
UpdateRuleStats("objective: variable not used elsewhere");
|
|
if (coeff > 0) {
|
|
if (!IntersectDomainWith(var, Domain(MinOf(var)))) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!IntersectDomainWith(var, Domain(MaxOf(var)))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsFixed(var)) {
|
|
offset_change += coeff * MinOf(var);
|
|
var_to_constraints[var].erase(-1);
|
|
objective_map.erase(var);
|
|
continue;
|
|
}
|
|
|
|
const AffineRelation::Relation r = GetAffineRelation(var);
|
|
if (r.representative == var) continue;
|
|
|
|
objective_map.erase(var);
|
|
var_to_constraints[var].erase(-1);
|
|
|
|
// Do the substitution.
|
|
offset_change += coeff * r.offset;
|
|
const int64 new_coeff = objective_map[r.representative] += coeff * r.coeff;
|
|
|
|
// Process new term.
|
|
if (new_coeff == 0) {
|
|
objective_map.erase(r.representative);
|
|
var_to_constraints[r.representative].erase(-1);
|
|
} else {
|
|
var_to_constraints[r.representative].insert(-1);
|
|
if (IsFixed(r.representative)) {
|
|
offset_change += new_coeff * MinOf(r.representative);
|
|
var_to_constraints[r.representative].erase(-1);
|
|
objective_map.erase(r.representative);
|
|
}
|
|
}
|
|
}
|
|
|
|
Domain implied_domain(0);
|
|
int64 gcd(0);
|
|
|
|
// We need to sort the entries to be deterministic.
|
|
tmp_entries.clear();
|
|
for (const auto& entry : objective_map) {
|
|
tmp_entries.push_back(entry);
|
|
}
|
|
std::sort(tmp_entries.begin(), tmp_entries.end());
|
|
for (const auto& entry : tmp_entries) {
|
|
const int var = entry.first;
|
|
const int64 coeff = entry.second;
|
|
gcd = MathUtil::GCD64(gcd, std::abs(coeff));
|
|
implied_domain =
|
|
implied_domain.AdditionWith(DomainOf(var).MultiplicationBy(coeff))
|
|
.RelaxIfTooComplex();
|
|
}
|
|
|
|
// This is the new domain.
|
|
// Note that the domain never include the offset.
|
|
objective_domain = objective_domain.AdditionWith(Domain(-offset_change))
|
|
.IntersectionWith(implied_domain);
|
|
objective_domain =
|
|
objective_domain.SimplifyUsingImpliedDomain(implied_domain);
|
|
|
|
// Updat the offset.
|
|
objective_offset += offset_change;
|
|
|
|
// Maybe divide by GCD.
|
|
if (gcd > 1) {
|
|
for (auto& entry : objective_map) {
|
|
entry.second /= gcd;
|
|
}
|
|
objective_domain = objective_domain.InverseMultiplicationBy(gcd);
|
|
objective_offset /= static_cast<double>(gcd);
|
|
objective_scaling_factor *= static_cast<double>(gcd);
|
|
}
|
|
|
|
if (objective_domain.IsEmpty()) return false;
|
|
|
|
// Detect if the objective domain do not limit the "optimal" objective value.
|
|
// If this is true, then we can apply any reduction that reduce the objective
|
|
// value without any issues.
|
|
objective_domain_is_constraining =
|
|
!implied_domain
|
|
.IntersectionWith(Domain(kint64min, objective_domain.Max()))
|
|
.IsIncludedIn(objective_domain);
|
|
return true;
|
|
}
|
|
|
|
void PresolveContext::SubstituteVariableInObjective(
|
|
int var_in_equality, int64 coeff_in_equality,
|
|
const ConstraintProto& equality, std::vector<int>* new_vars_in_objective) {
|
|
CHECK(equality.enforcement_literal().empty());
|
|
CHECK(RefIsPositive(var_in_equality));
|
|
|
|
if (new_vars_in_objective != nullptr) new_vars_in_objective->clear();
|
|
|
|
// We can only "easily" substitute if the objective coefficient is a multiple
|
|
// of the one in the constraint.
|
|
const int64 coeff_in_objective =
|
|
gtl::FindOrDie(objective_map, var_in_equality);
|
|
CHECK_NE(coeff_in_equality, 0);
|
|
CHECK_EQ(coeff_in_objective % coeff_in_equality, 0);
|
|
const int64 multiplier = coeff_in_objective / coeff_in_equality;
|
|
|
|
for (int i = 0; i < equality.linear().vars().size(); ++i) {
|
|
int var = equality.linear().vars(i);
|
|
int64 coeff = equality.linear().coeffs(i);
|
|
if (!RefIsPositive(var)) {
|
|
var = NegatedRef(var);
|
|
coeff = -coeff;
|
|
}
|
|
if (var == var_in_equality) continue;
|
|
|
|
int64& map_ref = objective_map[var];
|
|
if (map_ref == 0 && new_vars_in_objective != nullptr) {
|
|
new_vars_in_objective->push_back(var);
|
|
}
|
|
map_ref -= coeff * multiplier;
|
|
|
|
if (map_ref == 0) {
|
|
objective_map.erase(var);
|
|
var_to_constraints[var].erase(-1);
|
|
} else {
|
|
var_to_constraints[var].insert(-1);
|
|
}
|
|
}
|
|
|
|
objective_map.erase(var_in_equality);
|
|
var_to_constraints[var_in_equality].erase(-1);
|
|
|
|
// Deal with the offset.
|
|
Domain offset = ReadDomainFromProto(equality.linear());
|
|
DCHECK_EQ(offset.Min(), offset.Max());
|
|
bool exact = true;
|
|
offset = offset.MultiplicationBy(multiplier, &exact);
|
|
CHECK(exact);
|
|
|
|
// Tricky: The objective domain is without the offset, so we need to shift it
|
|
objective_offset += static_cast<double>(offset.Min());
|
|
objective_domain = objective_domain.AdditionWith(Domain(-offset.Min()));
|
|
|
|
// Because we can assume that the constraint we used was constraining
|
|
// (otherwise it would have been removed), the objective domain should be now
|
|
// constraining.
|
|
objective_domain_is_constraining = true;
|
|
}
|
|
|
|
void PresolveContext::WriteObjectiveToProto() {
|
|
if (objective_domain.IsEmpty()) {
|
|
return (void)NotifyThatModelIsUnsat();
|
|
}
|
|
|
|
// We need to sort the entries to be deterministic.
|
|
std::vector<std::pair<int, int64>> entries;
|
|
for (const auto& entry : objective_map) {
|
|
entries.push_back(entry);
|
|
}
|
|
std::sort(entries.begin(), entries.end());
|
|
|
|
CpObjectiveProto* mutable_obj = working_model->mutable_objective();
|
|
mutable_obj->set_offset(objective_offset);
|
|
mutable_obj->set_scaling_factor(objective_scaling_factor);
|
|
FillDomainInProto(objective_domain, mutable_obj);
|
|
mutable_obj->clear_vars();
|
|
mutable_obj->clear_coeffs();
|
|
for (const auto& entry : entries) {
|
|
mutable_obj->add_vars(entry.first);
|
|
mutable_obj->add_coeffs(entry.second);
|
|
}
|
|
}
|
|
|
|
} // namespace sat
|
|
} // namespace operations_research
|