Files
ortools-clone/ortools/sat/sat_decision.cc
2018-11-10 18:00:53 +01:00

363 lines
14 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/sat_decision.h"
#include "ortools/sat/util.h"
namespace operations_research {
namespace sat {
SatDecisionPolicy::SatDecisionPolicy(Model* model)
: parameters_(*(model->GetOrCreate<SatParameters>())),
trail_(model->GetOrCreate<Trail>()),
random_(model->GetOrCreate<ModelRandomGenerator>()) {}
void SatDecisionPolicy::IncreaseNumVariables(int num_variables) {
const int old_num_variables = activities_.size();
DCHECK_GE(num_variables, activities_.size());
activities_.resize(num_variables, parameters_.initial_variables_activity());
tie_breakers_.resize(num_variables, 0.0);
num_bumps_.resize(num_variables, 0);
pq_need_update_for_var_at_trail_index_.IncreaseSize(num_variables);
weighted_sign_.resize(num_variables, 0.0);
var_polarity_.resize(num_variables);
var_use_phase_saving_.resize(num_variables, parameters_.use_phase_saving());
ResetInitialPolarity(/*from=*/old_num_variables);
// Update the priority queue. Note that each addition is in O(1) because
// the priority is 0.0.
var_ordering_.Reserve(num_variables);
if (var_ordering_is_initialized_) {
for (BooleanVariable var(old_num_variables); var < num_variables; ++var) {
var_ordering_.Add({var, 0.0, activities_[var]});
}
}
}
void SatDecisionPolicy::ResetDecisionHeuristic() {
const int num_variables = activities_.size();
variable_activity_increment_ = 1.0;
activities_.assign(num_variables, parameters_.initial_variables_activity());
tie_breakers_.assign(num_variables, 0.0);
num_bumps_.assign(num_variables, 0);
var_ordering_.Clear();
ResetInitialPolarity(/*from=*/0);
var_use_phase_saving_.assign(num_variables, parameters_.use_phase_saving());
num_conflicts_ = 0;
num_conflicts_stack_.clear();
var_ordering_is_initialized_ = false;
}
void SatDecisionPolicy::ResetInitialPolarity(int from) {
// Sets the initial polarity.
//
// TODO(user): The WEIGHTED_SIGN one are currently slightly broken because the
// weighted_sign_ is updated after this has been called. It requires a call
// to ResetDecisionHeuristic() after all the constraint have been added. Fix.
// On another hand, this is only used with SolveWithRandomParameters() that
// does call this function.
const int num_variables = activities_.size();
for (BooleanVariable var(from); var < num_variables; ++var) {
switch (parameters_.initial_polarity()) {
case SatParameters::POLARITY_TRUE:
var_polarity_[var] = true;
break;
case SatParameters::POLARITY_FALSE:
var_polarity_[var] = false;
break;
case SatParameters::POLARITY_RANDOM:
var_polarity_[var] = std::uniform_int_distribution<int>(0, 1)(*random_);
break;
case SatParameters::POLARITY_WEIGHTED_SIGN:
var_polarity_[var] = weighted_sign_[var] > 0;
break;
case SatParameters::POLARITY_REVERSE_WEIGHTED_SIGN:
var_polarity_[var] = weighted_sign_[var] < 0;
break;
}
// TODO(user): this is the only non-const operation done by this class
// on the trail. Try to remove it?
trail_->SetLastPolarity(var, var_polarity_[var]);
}
}
void SatDecisionPolicy::InitializeVariableOrdering() {
const int num_variables = activities_.size();
// First, extract the variables without activity, and add the other to the
// priority queue.
var_ordering_.Clear();
std::vector<BooleanVariable> variables;
for (BooleanVariable var(0); var < num_variables; ++var) {
if (!trail_->Assignment().VariableIsAssigned(var)) {
if (activities_[var] > 0.0) {
var_ordering_.Add(
{var, static_cast<float>(tie_breakers_[var]), activities_[var]});
} else {
variables.push_back(var);
}
}
}
// Set the order of the other according to the parameters_.
// Note that this is just a "preference" since the priority queue will kind
// of randomize this. However, it is more efficient than using the tie_breaker
// which add a big overhead on the priority queue.
//
// TODO(user): Experiment and come up with a good set of heuristics.
switch (parameters_.preferred_variable_order()) {
case SatParameters::IN_ORDER:
break;
case SatParameters::IN_REVERSE_ORDER:
std::reverse(variables.begin(), variables.end());
break;
case SatParameters::IN_RANDOM_ORDER:
std::shuffle(variables.begin(), variables.end(), *random_);
break;
}
// Add the variables without activity to the queue (in the default order)
for (const BooleanVariable var : variables) {
var_ordering_.Add({var, static_cast<float>(tie_breakers_[var]), 0.0});
}
// Finish the queue initialization.
pq_need_update_for_var_at_trail_index_.ClearAndResize(num_variables);
pq_need_update_for_var_at_trail_index_.SetAllBefore(trail_->Index());
var_ordering_is_initialized_ = true;
}
void SatDecisionPolicy::SetAssignmentPreference(Literal literal,
double weight) {
if (!parameters_.use_optimization_hints()) return;
DCHECK_GE(weight, 0.0);
DCHECK_LE(weight, 1.0);
var_use_phase_saving_[literal.Variable()] = false;
var_polarity_[literal.Variable()] = literal.IsPositive();
// The tie_breaker is changed, so we need to reinitialize the priority queue.
// Note that this doesn't change the activity though.
tie_breakers_[literal.Variable()] = weight;
var_ordering_is_initialized_ = false;
}
std::vector<std::pair<Literal, double>> SatDecisionPolicy::AllPreferences()
const {
std::vector<std::pair<Literal, double>> prefs;
for (BooleanVariable var(0); var < var_polarity_.size(); ++var) {
// TODO(user): we currently assume that if the tie_breaker is zero then
// no preference was set (which is not 100% correct). Fix that.
const double value = var_ordering_.GetElement(var.value()).tie_breaker;
if (value > 0.0) {
prefs.push_back(std::make_pair(Literal(var, var_polarity_[var]), value));
}
}
return prefs;
}
void SatDecisionPolicy::UpdateWeightedSign(
const std::vector<LiteralWithCoeff>& terms, Coefficient rhs) {
for (const LiteralWithCoeff& term : terms) {
const double weight = static_cast<double>(term.coefficient.value()) /
static_cast<double>(rhs.value());
weighted_sign_[term.literal.Variable()] +=
term.literal.IsPositive() ? -weight : weight;
}
}
void SatDecisionPolicy::BumpVariableActivities(
const std::vector<Literal>& literals) {
if (parameters_.use_erwa_heuristic()) {
for (const Literal literal : literals) {
// Note that we don't really need to bump level 0 variables since they
// will never be backtracked over. However it is faster to simply bump
// them.
++num_bumps_[literal.Variable()];
}
return;
}
const double max_activity_value = parameters_.max_variable_activity_value();
for (const Literal literal : literals) {
const BooleanVariable var = literal.Variable();
const int level = trail_->Info(var).level;
if (level == 0) continue;
activities_[var] += variable_activity_increment_;
pq_need_update_for_var_at_trail_index_.Set(trail_->Info(var).trail_index);
if (activities_[var] > max_activity_value) {
RescaleVariableActivities(1.0 / max_activity_value);
}
}
}
void SatDecisionPolicy::RescaleVariableActivities(double scaling_factor) {
variable_activity_increment_ *= scaling_factor;
for (BooleanVariable var(0); var < activities_.size(); ++var) {
activities_[var] *= scaling_factor;
}
// When rescaling the activities of all the variables, the order of the
// active variables in the heap will not change, but we still need to update
// their weights so that newly inserted elements will compare correctly with
// already inserted ones.
//
// IMPORTANT: we need to reset the full heap from scratch because just
// multiplying the current weight by scaling_factor is not guaranteed to
// preserve the order. This is because the activity of two entries may go to
// zero and the tie-breaking ordering may change their relative order.
//
// InitializeVariableOrdering() will be called lazily only if needed.
var_ordering_is_initialized_ = false;
}
void SatDecisionPolicy::UpdateVariableActivityIncrement() {
variable_activity_increment_ *= 1.0 / parameters_.variable_activity_decay();
}
Literal SatDecisionPolicy::NextBranch() {
// Lazily initialize var_ordering_ if needed.
if (!var_ordering_is_initialized_) {
InitializeVariableOrdering();
}
// Choose the variable.
BooleanVariable var;
const double ratio = parameters_.random_branches_ratio();
auto zero_to_one = [this]() {
return std::uniform_real_distribution<double>()(*random_);
};
if (ratio != 0.0 && zero_to_one() < ratio) {
while (true) {
// TODO(user): This may not be super efficient if almost all the
// variables are assigned.
std::uniform_int_distribution<int> index_dist(0,
var_ordering_.Size() - 1);
var = var_ordering_.QueueElement(index_dist(*random_)).var;
if (!trail_->Assignment().VariableIsAssigned(var)) break;
pq_need_update_for_var_at_trail_index_.Set(trail_->Info(var).trail_index);
var_ordering_.Remove(var.value());
}
} else {
// The loop is done this way in order to leave the final choice in the heap.
DCHECK(!var_ordering_.IsEmpty());
var = var_ordering_.Top().var;
while (trail_->Assignment().VariableIsAssigned(var)) {
var_ordering_.Pop();
pq_need_update_for_var_at_trail_index_.Set(trail_->Info(var).trail_index);
DCHECK(!var_ordering_.IsEmpty());
var = var_ordering_.Top().var;
}
}
// Choose its polarity (i.e. True of False).
const double random_ratio = parameters_.random_polarity_ratio();
if (random_ratio != 0.0 && zero_to_one() < random_ratio) {
return Literal(var, std::uniform_int_distribution<int>(0, 1)(*random_));
}
return Literal(var, var_use_phase_saving_[var]
? trail_->Info(var).last_polarity
: var_polarity_[var]);
}
void SatDecisionPolicy::PqInsertOrUpdate(BooleanVariable var) {
const WeightedVarQueueElement element{
var, static_cast<float>(tie_breakers_[var]), activities_[var]};
if (var_ordering_.Contains(var.value())) {
// Note that the new weight should always be higher than the old one.
var_ordering_.IncreasePriority(element);
} else {
var_ordering_.Add(element);
}
}
void SatDecisionPolicy::Untrail(int target_trail_index) {
DCHECK_LT(target_trail_index, trail_->Index());
if (parameters_.use_erwa_heuristic()) {
// The ERWA parameter between the new estimation of the learning rate and
// the old one. TODO(user): Expose parameters for these values.
const double alpha = std::max(0.06, 0.4 - 1e-6 * num_conflicts_);
// This counts the number of conflicts since the assignment of the variable
// at the current trail_index that we are about to untrail.
int num_conflicts = 0;
int next_num_conflicts_update =
num_conflicts_stack_.empty() ? -1
: num_conflicts_stack_.back().trail_index;
int trail_index = trail_->Index();
while (trail_index > target_trail_index) {
if (next_num_conflicts_update == trail_index) {
num_conflicts += num_conflicts_stack_.back().count;
num_conflicts_stack_.pop_back();
next_num_conflicts_update =
num_conflicts_stack_.empty()
? -1
: num_conflicts_stack_.back().trail_index;
}
const BooleanVariable var = (*trail_)[--trail_index].Variable();
// TODO(user): This heuristic can make this code quite slow because
// all the untrailed variable will cause a priority queue update.
const int64 num_bumps = num_bumps_[var];
double new_rate = 0.0;
if (num_bumps > 0) {
DCHECK_GT(num_conflicts, 0);
num_bumps_[var] = 0;
new_rate = static_cast<double>(num_bumps) / num_conflicts;
}
activities_[var] = alpha * new_rate + (1 - alpha) * activities_[var];
if (var_ordering_is_initialized_) PqInsertOrUpdate(var);
}
if (num_conflicts > 0) {
if (!num_conflicts_stack_.empty() &&
num_conflicts_stack_.back().trail_index == trail_->Index()) {
num_conflicts_stack_.back().count += num_conflicts;
} else {
num_conflicts_stack_.push_back({trail_->Index(), num_conflicts});
}
}
} else {
if (!var_ordering_is_initialized_) return;
// Trail index of the next variable that will need a priority queue update.
int to_update = pq_need_update_for_var_at_trail_index_.Top();
while (to_update >= target_trail_index) {
DCHECK_LT(to_update, trail_->Index());
PqInsertOrUpdate((*trail_)[to_update].Variable());
pq_need_update_for_var_at_trail_index_.ClearTop();
to_update = pq_need_update_for_var_at_trail_index_.Top();
}
}
// Invariant.
if (DEBUG_MODE && var_ordering_is_initialized_) {
for (int trail_index = trail_->Index() - 1;
trail_index > target_trail_index; --trail_index) {
const BooleanVariable var = (*trail_)[trail_index].Variable();
CHECK(var_ordering_.Contains(var.value()));
CHECK_EQ(activities_[var], var_ordering_.GetElement(var.value()).weight);
}
}
}
} // namespace sat
} // namespace operations_research