Files
ortools-clone/ortools/sat/implied_bounds.cc
2019-11-25 12:02:25 +01:00

181 lines
6.7 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/implied_bounds.h"
namespace operations_research {
namespace sat {
// Just display some global statistics on destruction.
ImpliedBounds::~ImpliedBounds() {
VLOG(1) << num_deductions_ << " enqueued deductions.";
VLOG(1) << bounds_.size() << " implied bounds stored.";
VLOG(1) << num_enqueued_in_var_to_bounds_
<< " implied bounds with view stored.";
}
void ImpliedBounds::Add(Literal literal, IntegerLiteral integer_literal) {
if (!parameters_.use_implied_bounds()) return;
const IntegerVariable var = integer_literal.var;
// Update our local level-zero bound.
if (var >= level_zero_lower_bounds_.size()) {
level_zero_lower_bounds_.resize(var.value() + 1, kMinIntegerValue);
new_level_zero_bounds_.Resize(var + 1);
}
level_zero_lower_bounds_[var] = std::max(
level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
// Ignore any Add() with a bound worse than the level zero one.
// TODO(user): Check that this never happen? it shouldn't.
if (integer_literal.bound <= level_zero_lower_bounds_[var]) {
return;
}
// We skip any IntegerLiteral refering to a variable with only two consecutive
// possible values. This is because, once shifted this will already be a
// variable in [0, 1] so we shouldn't gain much by substituing it.
if (integer_trail_->LevelZeroLowerBound(var) + 1 >=
integer_trail_->LevelZeroUpperBound(var)) {
return;
}
// Add or update the current bound.
const auto key = std::make_pair(literal.Index(), var);
auto insert_result = bounds_.insert({key, integer_literal.bound});
if (!insert_result.second) {
if (insert_result.first->second < integer_literal.bound) {
insert_result.first->second = integer_literal.bound;
} else {
// No new info.
return;
}
}
// Check if we have any deduction. Since at least one of (literal,
// literal.Negated()) must be true, we can take the min bound as valid at
// level zero.
//
// TODO(user): Like in probing, we can also create hole in the domain if there
// is some implied bounds for (literal.NegatedIndex, NegagtionOf(var)) that
// crosses integer_literal.bound.
const auto it = bounds_.find(std::make_pair(literal.NegatedIndex(), var));
if (it != bounds_.end()) {
if (it->second <= level_zero_lower_bounds_[var]) {
// The other bounds is worse than the new level-zero bound which can
// happen because of lazy update, so here we just remove it.
bounds_.erase(it);
} else {
const IntegerValue deduction =
std::min(integer_literal.bound, it->second);
DCHECK_GT(deduction, level_zero_lower_bounds_[var]);
DCHECK_GT(deduction, integer_trail_->LevelZeroLowerBound(var));
// TODO(user): support Enqueueing level zero fact at a positive level.
// That is, do not loose the info on backtrack. This should be doable. It
// is also why we return a bool in case of conflict when pushing
// deduction.
++num_deductions_;
level_zero_lower_bounds_[var] = deduction;
new_level_zero_bounds_.Set(var);
VLOG(1) << "Deduction old: "
<< IntegerLiteral::GreaterOrEqual(
var, integer_trail_->LevelZeroLowerBound(var))
<< " new: " << IntegerLiteral::GreaterOrEqual(var, deduction);
// The entries that are equal to the min no longer need to be stored once
// the level zero bound is enqueued.
if (it->second == deduction) {
bounds_.erase(it);
}
if (integer_literal.bound == deduction) {
bounds_.erase(std::make_pair(literal.Index(), var));
// No need to update var_to_bounds_ in this case.
return;
}
}
}
// If we have a new implied bound and the literal has a view, add it to
// var_to_bounds_. Note that we might add more than one entry with the same
// literal_view, and we will later need to lazily clean the vector up.
if (integer_encoder_->GetLiteralView(literal) != kNoIntegerVariable) {
if (var_to_bounds_.size() <= var) {
var_to_bounds_.resize(var.value() + 1);
}
++num_enqueued_in_var_to_bounds_;
var_to_bounds_[var].push_back({integer_encoder_->GetLiteralView(literal),
integer_literal.bound, true});
} else if (integer_encoder_->GetLiteralView(literal.Negated()) !=
kNoIntegerVariable) {
if (var_to_bounds_.size() <= var) {
var_to_bounds_.resize(var.value() + 1);
}
++num_enqueued_in_var_to_bounds_;
var_to_bounds_[var].push_back(
{integer_encoder_->GetLiteralView(literal.Negated()),
integer_literal.bound, false});
}
}
const std::vector<ImpliedBoundEntry>& ImpliedBounds::GetImpliedBounds(
IntegerVariable var) {
if (var >= var_to_bounds_.size()) return empty_implied_bounds_;
// Lazily remove obsolete entries from the vector.
// TODO(user): Check no duplicate and remove old entry if the enforcement
// is tighter.
int new_size = 0;
std::vector<ImpliedBoundEntry>& ref = var_to_bounds_[var];
const IntegerValue level_zero_lb = std::max(
level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
level_zero_lower_bounds_[var] = level_zero_lb;
for (const ImpliedBoundEntry& entry : ref) {
if (entry.lower_bound <= level_zero_lb) continue;
ref[new_size++] = entry;
}
ref.resize(new_size);
return ref;
}
void ImpliedBounds::ProcessIntegerTrail(Literal first_decision) {
if (!parameters_.use_implied_bounds()) return;
CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 1);
tmp_integer_literals_.clear();
integer_trail_->AppendNewBounds(&tmp_integer_literals_);
for (const IntegerLiteral lit : tmp_integer_literals_) {
Add(first_decision, lit);
}
}
bool ImpliedBounds::EnqueueNewDeductions() {
CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
for (const IntegerVariable var :
new_level_zero_bounds_.PositionsSetAtLeastOnce()) {
if (!integer_trail_->Enqueue(
IntegerLiteral::GreaterOrEqual(var, level_zero_lower_bounds_[var]),
{}, {})) {
return false;
}
}
new_level_zero_bounds_.SparseClearAll();
return sat_solver_->FinishPropagation();
}
} // namespace sat
} // namespace operations_research