2016-03-16 10:10:38 +01:00
|
|
|
// Copyright 2010-2014 Google
|
|
|
|
|
// 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.
|
|
|
|
|
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/sat/integer.h"
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2017-04-28 16:17:22 +02:00
|
|
|
#include "ortools/base/iterator_adaptors.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/base/stl_util.h"
|
2016-03-16 10:10:38 +01:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace sat {
|
|
|
|
|
|
2016-12-20 18:23:57 +01:00
|
|
|
std::vector<IntegerVariable> NegationOf(
|
|
|
|
|
const std::vector<IntegerVariable>& vars) {
|
|
|
|
|
std::vector<IntegerVariable> result(vars.size());
|
|
|
|
|
for (int i = 0; i < vars.size(); ++i) {
|
|
|
|
|
result[i] = NegationOf(vars[i]);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
void IntegerEncoder::FullyEncodeVariable(IntegerVariable i_var,
|
|
|
|
|
std::vector<IntegerValue> values) {
|
|
|
|
|
CHECK_EQ(0, sat_solver_->CurrentDecisionLevel());
|
|
|
|
|
CHECK(!values.empty()); // UNSAT problem. We don't deal with that here.
|
|
|
|
|
|
|
|
|
|
STLSortAndRemoveDuplicates(&values);
|
|
|
|
|
|
|
|
|
|
// TODO(user): This case is annoying, not sure yet how to best fix the
|
|
|
|
|
// variable. There is certainly no need to create a Boolean variable, but
|
|
|
|
|
// one needs to talk to IntegerTrail to fix the variable and we don't want
|
|
|
|
|
// the encoder to depend on this. So for now we fail here and it is up to
|
|
|
|
|
// the caller to deal with this case.
|
|
|
|
|
CHECK_NE(values.size(), 1);
|
|
|
|
|
|
2016-10-12 12:07:20 -07:00
|
|
|
// If the variable has already been fully encoded, we set to false the
|
|
|
|
|
// literals that cannot be true anymore. We also log a warning because ideally
|
|
|
|
|
// these intersection should happen in the presolve.
|
2016-09-22 13:55:16 +02:00
|
|
|
if (ContainsKey(full_encoding_index_, i_var)) {
|
2016-10-12 12:07:20 -07:00
|
|
|
int num_fixed = 0;
|
2017-04-27 14:14:05 +02:00
|
|
|
std::unordered_set<IntegerValue> to_interset(values.begin(), values.end());
|
2016-09-22 13:55:16 +02:00
|
|
|
const std::vector<ValueLiteralPair>& encoding = FullDomainEncoding(i_var);
|
2016-10-12 12:07:20 -07:00
|
|
|
for (const ValueLiteralPair& p : encoding) {
|
|
|
|
|
if (!ContainsKey(to_interset, p.value)) {
|
|
|
|
|
// TODO(user): also remove this entry from encoding.
|
|
|
|
|
++num_fixed;
|
|
|
|
|
sat_solver_->AddUnitClause(p.literal.Negated());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (num_fixed > 0) {
|
2016-11-10 13:13:56 +01:00
|
|
|
LOG(WARNING) << "Domain intersection removed " << num_fixed << " values "
|
|
|
|
|
<< "(out of " << encoding.size() << ").";
|
2016-09-22 13:55:16 +02:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<ValueLiteralPair> encoding;
|
|
|
|
|
if (values.size() == 2) {
|
|
|
|
|
const BooleanVariable var = sat_solver_->NewBooleanVariable();
|
|
|
|
|
encoding.push_back({values[0], Literal(var, true)});
|
|
|
|
|
encoding.push_back({values[1], Literal(var, false)});
|
|
|
|
|
} else {
|
|
|
|
|
std::vector<sat::LiteralWithCoeff> cst;
|
|
|
|
|
for (const IntegerValue value : values) {
|
|
|
|
|
const BooleanVariable var = sat_solver_->NewBooleanVariable();
|
|
|
|
|
encoding.push_back({value, Literal(var, true)});
|
|
|
|
|
cst.push_back(LiteralWithCoeff(Literal(var, true), Coefficient(1)));
|
|
|
|
|
}
|
|
|
|
|
CHECK(sat_solver_->AddLinearConstraint(true, sat::Coefficient(1), true,
|
|
|
|
|
sat::Coefficient(1), &cst));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
full_encoding_index_[i_var] = full_encoding_.size();
|
|
|
|
|
full_encoding_.push_back(encoding); // copy because we need it below.
|
|
|
|
|
|
|
|
|
|
// Deal with NegationOf(i_var).
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): This seems a bit wasted, but it does simplify the code at a
|
|
|
|
|
// somehow small cost.
|
|
|
|
|
std::reverse(encoding.begin(), encoding.end());
|
|
|
|
|
for (auto& entry : encoding) {
|
|
|
|
|
entry.value = -entry.value; // Reverse the value.
|
|
|
|
|
}
|
|
|
|
|
full_encoding_index_[NegationOf(i_var)] = full_encoding_.size();
|
|
|
|
|
full_encoding_.push_back(std::move(encoding));
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 13:53:30 +02:00
|
|
|
void IntegerEncoder::FullyEncodeVariable(IntegerVariable i_var, IntegerValue lb,
|
|
|
|
|
IntegerValue ub) {
|
|
|
|
|
// TODO(user): optimize the code if it ever become needed.
|
|
|
|
|
CHECK_LE(ub - lb, 10000) << "Large domain for full encoding! investigate.";
|
|
|
|
|
std::vector<IntegerValue> values;
|
|
|
|
|
for (IntegerValue value = lb; value <= ub; ++value) values.push_back(value);
|
|
|
|
|
return FullyEncodeVariable(i_var, std::move(values));
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-01 16:28:29 +02:00
|
|
|
// TODO(user): merge the common code with FullyEncodeVariable().
|
2016-11-07 11:59:39 +01:00
|
|
|
void IntegerEncoder::FullyEncodeVariableUsingGivenLiterals(
|
|
|
|
|
IntegerVariable i_var, const std::vector<Literal>& literals,
|
|
|
|
|
const std::vector<IntegerValue>& values) {
|
|
|
|
|
CHECK(!VariableIsFullyEncoded(i_var));
|
|
|
|
|
|
|
|
|
|
// Sort the literal.
|
|
|
|
|
std::vector<ValueLiteralPair> encoding;
|
|
|
|
|
std::vector<sat::LiteralWithCoeff> cst;
|
|
|
|
|
for (int i = 0; i < values.size(); ++i) {
|
|
|
|
|
const Literal literal = literals[i];
|
|
|
|
|
const IntegerValue value = values[i];
|
|
|
|
|
encoding.push_back({value, literal});
|
|
|
|
|
cst.push_back(LiteralWithCoeff(literal, Coefficient(1)));
|
|
|
|
|
}
|
|
|
|
|
std::sort(encoding.begin(), encoding.end());
|
|
|
|
|
|
2017-06-01 16:28:29 +02:00
|
|
|
// We need the <= 1 constraint, and the >= 1 part is cheap. Note that the
|
|
|
|
|
// solver will discard it if it is of size 2 and contains a literal and its
|
|
|
|
|
// negation.
|
|
|
|
|
CHECK(sat_solver_->AddLinearConstraint(true, sat::Coefficient(1), true,
|
|
|
|
|
sat::Coefficient(1), &cst));
|
|
|
|
|
|
2016-11-07 11:59:39 +01:00
|
|
|
full_encoding_index_[i_var] = full_encoding_.size();
|
|
|
|
|
full_encoding_.push_back(encoding); // copy because we need it below.
|
|
|
|
|
|
|
|
|
|
// Deal with NegationOf(i_var).
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): This seems a bit wasted, but it does simplify the code at a
|
|
|
|
|
// somehow small cost.
|
|
|
|
|
std::reverse(encoding.begin(), encoding.end());
|
|
|
|
|
for (auto& entry : encoding) {
|
|
|
|
|
entry.value = -entry.value; // Reverse the value.
|
|
|
|
|
}
|
|
|
|
|
full_encoding_index_[NegationOf(i_var)] = full_encoding_.size();
|
|
|
|
|
full_encoding_.push_back(std::move(encoding));
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-12 13:42:16 +02:00
|
|
|
void IntegerEncoder::AddImplications(IntegerLiteral i_lit, Literal literal) {
|
|
|
|
|
if (i_lit.var >= encoding_by_var_.size()) {
|
|
|
|
|
encoding_by_var_.resize(i_lit.var + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::map<IntegerValue, Literal>& map_ref =
|
|
|
|
|
encoding_by_var_[IntegerVariable(i_lit.var)];
|
|
|
|
|
CHECK(!ContainsKey(map_ref, i_lit.bound));
|
|
|
|
|
|
|
|
|
|
auto after_it = map_ref.lower_bound(i_lit.bound);
|
|
|
|
|
if (after_it != map_ref.end()) {
|
|
|
|
|
// Literal(after) => literal
|
2016-11-10 13:13:56 +01:00
|
|
|
if (sat_solver_->CurrentDecisionLevel() == 0) {
|
|
|
|
|
sat_solver_->AddBinaryClause(after_it->second.Negated(), literal);
|
|
|
|
|
} else {
|
|
|
|
|
sat_solver_->AddBinaryClauseDuringSearch(after_it->second.Negated(),
|
|
|
|
|
literal);
|
|
|
|
|
}
|
2016-09-12 13:42:16 +02:00
|
|
|
}
|
|
|
|
|
if (after_it != map_ref.begin()) {
|
|
|
|
|
// literal => Literal(before)
|
2016-11-10 13:13:56 +01:00
|
|
|
if (sat_solver_->CurrentDecisionLevel() == 0) {
|
|
|
|
|
sat_solver_->AddBinaryClause(literal.Negated(), (--after_it)->second);
|
|
|
|
|
} else {
|
|
|
|
|
sat_solver_->AddBinaryClauseDuringSearch(literal.Negated(),
|
|
|
|
|
(--after_it)->second);
|
|
|
|
|
}
|
2016-09-12 13:42:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the new entry.
|
|
|
|
|
map_ref[i_lit.bound] = literal;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-07 17:29:33 +02:00
|
|
|
void IntegerEncoder::AssociateGivenLiteral(IntegerLiteral i_lit,
|
|
|
|
|
Literal literal) {
|
2016-09-12 13:42:16 +02:00
|
|
|
// Resize reverse encoding.
|
|
|
|
|
const int new_size =
|
|
|
|
|
1 + std::max(literal.Index(), literal.NegatedIndex()).value();
|
|
|
|
|
if (new_size > reverse_encoding_.size()) reverse_encoding_.resize(new_size);
|
|
|
|
|
|
|
|
|
|
// Associate the new literal to i_lit.
|
|
|
|
|
AddImplications(i_lit, literal);
|
2016-10-13 10:44:35 -07:00
|
|
|
reverse_encoding_[literal.Index()].push_back(i_lit);
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2016-09-12 13:42:16 +02:00
|
|
|
// Add its negation and associated it with i_lit.Negated().
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): This seems to work for optional variables, but I am not
|
|
|
|
|
// 100% sure why!! I think it works because these literals can only appear
|
|
|
|
|
// in a conflict if the presence literal of the optional variables is true.
|
|
|
|
|
AddImplications(i_lit.Negated(), literal.Negated());
|
2016-10-13 10:44:35 -07:00
|
|
|
reverse_encoding_[literal.NegatedIndex()].push_back(i_lit.Negated());
|
2016-10-07 17:29:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Literal IntegerEncoder::CreateAssociatedLiteral(IntegerLiteral i_lit) {
|
|
|
|
|
++num_created_variables_;
|
|
|
|
|
const BooleanVariable new_var = sat_solver_->NewBooleanVariable();
|
|
|
|
|
const Literal literal(new_var, true);
|
|
|
|
|
AssociateGivenLiteral(i_lit, literal);
|
2016-09-12 13:42:16 +02:00
|
|
|
return literal;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-07 17:29:33 +02:00
|
|
|
bool IntegerEncoder::LiteralIsAssociated(IntegerLiteral i) const {
|
|
|
|
|
if (i.var >= encoding_by_var_.size()) return false;
|
|
|
|
|
const std::map<IntegerValue, Literal>& encoding =
|
|
|
|
|
encoding_by_var_[IntegerVariable(i.var)];
|
|
|
|
|
return encoding.find(i.bound) != encoding.end();
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-29 14:45:17 +02:00
|
|
|
LiteralIndex IntegerEncoder::GetAssociatedLiteral(IntegerLiteral i) {
|
|
|
|
|
if (i.var >= encoding_by_var_.size()) return kNoLiteralIndex;
|
|
|
|
|
const std::map<IntegerValue, Literal>& encoding =
|
|
|
|
|
encoding_by_var_[IntegerVariable(i.var)];
|
|
|
|
|
const auto result = encoding.find(i.bound);
|
|
|
|
|
if (result == encoding.end()) return kNoLiteralIndex;
|
|
|
|
|
return result->second.Index();
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-07 17:29:33 +02:00
|
|
|
Literal IntegerEncoder::GetOrCreateAssociatedLiteral(IntegerLiteral i) {
|
|
|
|
|
if (i.var < encoding_by_var_.size()) {
|
|
|
|
|
const std::map<IntegerValue, Literal>& encoding =
|
|
|
|
|
encoding_by_var_[IntegerVariable(i.var)];
|
|
|
|
|
const auto it = encoding.find(i.bound);
|
|
|
|
|
if (it != encoding.end()) return it->second;
|
|
|
|
|
}
|
|
|
|
|
return CreateAssociatedLiteral(i);
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-12 13:42:16 +02:00
|
|
|
LiteralIndex IntegerEncoder::SearchForLiteralAtOrBefore(
|
|
|
|
|
IntegerLiteral i) const {
|
|
|
|
|
// We take the element before the upper_bound() which is either the encoding
|
|
|
|
|
// of i if it already exists, or the encoding just before it.
|
|
|
|
|
if (i.var >= encoding_by_var_.size()) return kNoLiteralIndex;
|
|
|
|
|
const std::map<IntegerValue, Literal>& encoding =
|
|
|
|
|
encoding_by_var_[IntegerVariable(i.var)];
|
|
|
|
|
auto after_it = encoding.upper_bound(i.bound);
|
|
|
|
|
if (after_it == encoding.begin()) return kNoLiteralIndex;
|
|
|
|
|
--after_it;
|
|
|
|
|
return after_it->second.Index();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IntegerTrail::Propagate(Trail* trail) {
|
2016-03-16 10:10:38 +01:00
|
|
|
// Make sure that our internal "integer_decision_levels_" size matches the
|
|
|
|
|
// sat decision levels. At the level zero, integer_decision_levels_ should
|
|
|
|
|
// be empty.
|
|
|
|
|
if (trail->CurrentDecisionLevel() > integer_decision_levels_.size()) {
|
|
|
|
|
integer_decision_levels_.push_back(integer_trail_.size());
|
|
|
|
|
CHECK_EQ(trail->CurrentDecisionLevel(), integer_decision_levels_.size());
|
|
|
|
|
}
|
2016-09-12 13:42:16 +02:00
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
// Value encoder.
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): There is no need to maintain the bounds of such variable if
|
|
|
|
|
// they are never used in any constraint!
|
|
|
|
|
//
|
|
|
|
|
// Algorithm:
|
|
|
|
|
// 1/ See if new variables are fully encoded and initialize them.
|
|
|
|
|
// 2/ In the loop below, each time a "min" variable was assigned to false,
|
|
|
|
|
// update the associated variable bounds, and change the watched "min".
|
2017-06-01 16:28:29 +02:00
|
|
|
// This step is in O(num variables at false between the old and new min).
|
2016-09-22 13:55:16 +02:00
|
|
|
//
|
|
|
|
|
// The data structure are reversible.
|
|
|
|
|
watched_min_.SetLevel(trail->CurrentDecisionLevel());
|
|
|
|
|
current_min_.SetLevel(trail->CurrentDecisionLevel());
|
2016-12-08 14:58:18 +01:00
|
|
|
var_to_current_lb_interval_index_.SetLevel(trail->CurrentDecisionLevel());
|
2016-09-22 13:55:16 +02:00
|
|
|
if (encoder_->GetFullyEncodedVariables().size() != num_encoded_variables_) {
|
|
|
|
|
num_encoded_variables_ = encoder_->GetFullyEncodedVariables().size();
|
|
|
|
|
|
|
|
|
|
// for now this is only supported at level zero. Otherwise we need to
|
|
|
|
|
// inspect the trail to properly compute all the min.
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): Don't rescan all the variables from scratch, we could only
|
|
|
|
|
// scan the new ones. But then we need a mecanism to detect the new ones.
|
|
|
|
|
CHECK_EQ(trail->CurrentDecisionLevel(), 0);
|
|
|
|
|
for (const auto& entry : encoder_->GetFullyEncodedVariables()) {
|
2017-03-28 16:11:06 +02:00
|
|
|
const IntegerVariable var = entry.first;
|
|
|
|
|
if (IsCurrentlyIgnored(var)) continue;
|
2016-11-09 13:23:23 +01:00
|
|
|
|
|
|
|
|
// This variable was already added and will be processed below.
|
|
|
|
|
// Note that this is important, otherwise we may call many times
|
|
|
|
|
// watched_min_.Add() on the same literal.
|
|
|
|
|
if (ContainsKey(current_min_, var)) continue;
|
|
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
const auto& encoding = encoder_->FullDomainEncoding(var);
|
|
|
|
|
for (int i = 0; i < encoding.size(); ++i) {
|
|
|
|
|
if (!trail_->Assignment().LiteralIsFalse(encoding[i].literal)) {
|
2016-11-09 13:23:23 +01:00
|
|
|
if (!trail_->Assignment().LiteralIsTrue(encoding[i].literal)) {
|
|
|
|
|
watched_min_.Add(encoding[i].literal.NegatedIndex(), var);
|
|
|
|
|
}
|
2016-09-22 13:55:16 +02:00
|
|
|
current_min_.Set(var, i);
|
|
|
|
|
|
|
|
|
|
// No reason because we are at level zero.
|
|
|
|
|
if (!Enqueue(IntegerLiteral::GreaterOrEqual(var, encoding[i].value),
|
|
|
|
|
{}, {})) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-12 13:42:16 +02:00
|
|
|
// Process all the "associated" literals and Enqueue() the corresponding
|
|
|
|
|
// bounds.
|
|
|
|
|
while (propagation_trail_index_ < trail->Index()) {
|
|
|
|
|
const Literal literal = (*trail)[propagation_trail_index_++];
|
2016-09-22 13:55:16 +02:00
|
|
|
|
|
|
|
|
// Bound encoder.
|
2016-10-13 10:44:35 -07:00
|
|
|
for (const IntegerLiteral i_lit : encoder_->GetIntegerLiterals(literal)) {
|
2017-03-28 16:11:06 +02:00
|
|
|
if (IsCurrentlyIgnored(i_lit.Var())) continue;
|
|
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
// The reason is simply the associated literal.
|
|
|
|
|
if (!Enqueue(i_lit, {literal.Negated()}, {})) return false;
|
|
|
|
|
}
|
2016-09-12 13:42:16 +02:00
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
// Value encoder.
|
2017-03-28 16:11:06 +02:00
|
|
|
for (const IntegerVariable var : watched_min_.Values(literal.Index())) {
|
|
|
|
|
DCHECK(!IsOptional(var)) << "Not supported yet";
|
|
|
|
|
|
2016-11-09 13:23:23 +01:00
|
|
|
// A watched min value just became false. Note that because current_min_
|
|
|
|
|
// is also updated by Enqueue(), it may be larger than the watched min.
|
|
|
|
|
const int min = current_min_.FindOrDie(var);
|
|
|
|
|
int i = min;
|
2016-09-22 13:55:16 +02:00
|
|
|
const auto& encoding = encoder_->FullDomainEncoding(var);
|
2016-11-09 13:23:23 +01:00
|
|
|
tmp_literals_reason_.clear();
|
|
|
|
|
for (; i < encoding.size(); ++i) {
|
|
|
|
|
if (!trail_->Assignment().LiteralIsFalse(encoding[i].literal)) break;
|
|
|
|
|
tmp_literals_reason_.push_back(encoding[i].literal);
|
|
|
|
|
}
|
2016-09-22 13:55:16 +02:00
|
|
|
|
2016-11-09 13:23:23 +01:00
|
|
|
// Note(user): we enforce a "== 1" on the encoding literals, but the
|
|
|
|
|
// clause forcing at least one of them to be true may not have propagated
|
|
|
|
|
// in some cases (because we loop in the integer propagators before
|
|
|
|
|
// calling the clause propagator again).
|
|
|
|
|
if (i == encoding.size()) {
|
|
|
|
|
return ReportConflict(tmp_literals_reason_, {LowerBoundAsLiteral(var)});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note that we don't need to delete the old watched min:
|
|
|
|
|
// - its literal has been assigned, so it will not be queried again until
|
|
|
|
|
// backtrack.
|
|
|
|
|
// - When backtracked over, it will already be set correctly. It seems
|
|
|
|
|
// less efficient to delete it now and add it back on backtrack.
|
|
|
|
|
watched_min_.Add(encoding[i].literal.NegatedIndex(), var);
|
|
|
|
|
if (i > min) {
|
|
|
|
|
// Note that we also need the fact that all smaller value are false
|
|
|
|
|
// for the propagation. We use the current lower bound for that.
|
|
|
|
|
current_min_.Set(var, i);
|
|
|
|
|
if (!Enqueue(IntegerLiteral::GreaterOrEqual(var, encoding[i].value),
|
|
|
|
|
tmp_literals_reason_, {LowerBoundAsLiteral(var)})) {
|
|
|
|
|
return false;
|
2016-09-22 13:55:16 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-12 13:42:16 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-16 10:10:38 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IntegerTrail::Untrail(const Trail& trail, int literal_trail_index) {
|
2016-09-22 13:55:16 +02:00
|
|
|
watched_min_.SetLevel(trail.CurrentDecisionLevel());
|
|
|
|
|
current_min_.SetLevel(trail.CurrentDecisionLevel());
|
2016-12-08 14:58:18 +01:00
|
|
|
var_to_current_lb_interval_index_.SetLevel(trail.CurrentDecisionLevel());
|
2016-03-16 10:10:38 +01:00
|
|
|
propagation_trail_index_ =
|
|
|
|
|
std::min(propagation_trail_index_, literal_trail_index);
|
|
|
|
|
|
|
|
|
|
// Note that if a conflict was detected before Propagate() of this class was
|
|
|
|
|
// even called, it is possible that there is nothing to backtrack.
|
|
|
|
|
const int decision_level = trail.CurrentDecisionLevel();
|
|
|
|
|
if (decision_level >= integer_decision_levels_.size()) return;
|
|
|
|
|
const int target = integer_decision_levels_[decision_level];
|
|
|
|
|
integer_decision_levels_.resize(decision_level);
|
|
|
|
|
CHECK_GE(target, vars_.size());
|
|
|
|
|
|
|
|
|
|
// This is needed for the code below to work.
|
|
|
|
|
if (target == integer_trail_.size()) return;
|
|
|
|
|
|
|
|
|
|
for (int index = integer_trail_.size() - 1; index >= target; --index) {
|
|
|
|
|
const TrailEntry& entry = integer_trail_[index];
|
2016-10-20 22:05:03 +02:00
|
|
|
if (entry.var < 0) continue; // entry used by EnqueueLiteral().
|
2016-03-16 10:10:38 +01:00
|
|
|
vars_[entry.var].current_trail_index = entry.prev_trail_index;
|
|
|
|
|
vars_[entry.var].current_bound =
|
|
|
|
|
integer_trail_[entry.prev_trail_index].bound;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resize vectors.
|
|
|
|
|
literals_reason_buffer_.resize(
|
|
|
|
|
integer_trail_[target].literals_reason_start_index);
|
2016-10-20 22:05:03 +02:00
|
|
|
bounds_reason_buffer_.resize(integer_trail_[target].dependencies_start_index);
|
2016-03-16 10:10:38 +01:00
|
|
|
integer_trail_.resize(target);
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-12 13:42:16 +02:00
|
|
|
IntegerVariable IntegerTrail::AddIntegerVariable(IntegerValue lower_bound,
|
|
|
|
|
IntegerValue upper_bound) {
|
|
|
|
|
CHECK_GE(lower_bound, kMinIntegerValue);
|
|
|
|
|
CHECK_LE(lower_bound, kMaxIntegerValue);
|
|
|
|
|
CHECK_GE(upper_bound, kMinIntegerValue);
|
|
|
|
|
CHECK_LE(upper_bound, kMaxIntegerValue);
|
2016-03-16 10:10:38 +01:00
|
|
|
CHECK(integer_decision_levels_.empty());
|
|
|
|
|
CHECK_EQ(vars_.size(), integer_trail_.size());
|
|
|
|
|
|
2016-09-12 13:42:16 +02:00
|
|
|
const IntegerVariable i(vars_.size());
|
2017-03-28 16:11:06 +02:00
|
|
|
is_ignored_literals_.push_back(kNoLiteralIndex);
|
2016-03-16 10:10:38 +01:00
|
|
|
vars_.push_back({lower_bound, static_cast<int>(integer_trail_.size())});
|
2016-09-12 13:42:16 +02:00
|
|
|
integer_trail_.push_back({lower_bound, i.value()});
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
// TODO(user): the is_ignored_literals_ Booleans are currently always the same
|
|
|
|
|
// for a variable and its negation. So it may be better not to store it twice
|
|
|
|
|
// so that we don't have to be careful when setting them.
|
2016-09-12 13:42:16 +02:00
|
|
|
CHECK_EQ(NegationOf(i).value(), vars_.size());
|
2017-03-28 16:11:06 +02:00
|
|
|
is_ignored_literals_.push_back(kNoLiteralIndex);
|
2016-03-16 10:10:38 +01:00
|
|
|
vars_.push_back({-upper_bound, static_cast<int>(integer_trail_.size())});
|
2016-09-12 13:42:16 +02:00
|
|
|
integer_trail_.push_back({-upper_bound, NegationOf(i).value()});
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2016-09-12 13:42:16 +02:00
|
|
|
for (SparseBitset<IntegerVariable>* w : watchers_) {
|
|
|
|
|
w->Resize(NumIntegerVariables());
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-08 14:58:18 +01:00
|
|
|
IntegerVariable IntegerTrail::AddIntegerVariable(
|
|
|
|
|
const std::vector<ClosedInterval>& domain) {
|
|
|
|
|
CHECK(!domain.empty());
|
|
|
|
|
CHECK(IntervalsAreSortedAndDisjoint(domain));
|
|
|
|
|
const IntegerVariable var = AddIntegerVariable(
|
|
|
|
|
IntegerValue(domain.front().start), IntegerValue(domain.back().end));
|
|
|
|
|
|
|
|
|
|
// We only stores the vector of closed intervals for "complex" domain.
|
|
|
|
|
if (domain.size() > 1) {
|
|
|
|
|
var_to_current_lb_interval_index_.Set(var, all_intervals_.size());
|
|
|
|
|
for (const ClosedInterval interval : domain) {
|
|
|
|
|
all_intervals_.push_back(interval);
|
|
|
|
|
}
|
|
|
|
|
InsertOrDie(&var_to_end_interval_index_, var, all_intervals_.size());
|
|
|
|
|
|
|
|
|
|
// Copy for the negated variable.
|
|
|
|
|
var_to_current_lb_interval_index_.Set(NegationOf(var),
|
|
|
|
|
all_intervals_.size());
|
2017-04-28 16:17:22 +02:00
|
|
|
for (const ClosedInterval interval : ::gtl::reversed_view(domain)) {
|
2016-12-08 14:58:18 +01:00
|
|
|
all_intervals_.push_back({-interval.end, -interval.start});
|
|
|
|
|
}
|
|
|
|
|
InsertOrDie(&var_to_end_interval_index_, NegationOf(var),
|
|
|
|
|
all_intervals_.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return var;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(user): we could return a reference, but this is likely not in any
|
|
|
|
|
// critical code path.
|
|
|
|
|
std::vector<ClosedInterval> IntegerTrail::InitialVariableDomain(
|
|
|
|
|
IntegerVariable var) const {
|
|
|
|
|
const int interval_index =
|
|
|
|
|
FindWithDefault(var_to_current_lb_interval_index_, var, -1);
|
|
|
|
|
if (interval_index >= 0) {
|
|
|
|
|
return std::vector<ClosedInterval>(
|
|
|
|
|
&all_intervals_[interval_index],
|
|
|
|
|
&all_intervals_[FindOrDie(var_to_end_interval_index_, var)]);
|
|
|
|
|
} else {
|
|
|
|
|
std::vector<ClosedInterval> result;
|
|
|
|
|
result.push_back({LevelZeroBound(var.value()).value(),
|
|
|
|
|
-LevelZeroBound(NegationOf(var).value()).value()});
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-06 15:29:11 +01:00
|
|
|
IntegerVariable IntegerTrail::GetOrCreateConstantIntegerVariable(
|
|
|
|
|
IntegerValue value) {
|
|
|
|
|
auto insert = constant_map_.insert(std::make_pair(value, kNoIntegerVariable));
|
|
|
|
|
if (insert.second) { // new element.
|
|
|
|
|
const IntegerVariable new_var = AddIntegerVariable(value, value);
|
|
|
|
|
insert.first->second = new_var;
|
|
|
|
|
if (value != 0) InsertOrDie(&constant_map_, -value, NegationOf(new_var));
|
|
|
|
|
}
|
|
|
|
|
return insert.first->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int IntegerTrail::NumConstantVariables() const {
|
|
|
|
|
// The +1 if for the special key zero (the only case when we have an odd
|
|
|
|
|
// number of entries).
|
|
|
|
|
return (constant_map_.size() + 1) / 2;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 10:10:38 +01:00
|
|
|
int IntegerTrail::FindLowestTrailIndexThatExplainBound(
|
|
|
|
|
IntegerLiteral i_lit) const {
|
2016-10-20 22:05:03 +02:00
|
|
|
DCHECK_LE(i_lit.bound, vars_[i_lit.var].current_bound);
|
2016-03-16 10:10:38 +01:00
|
|
|
if (i_lit.bound <= LevelZeroBound(i_lit.var)) return -1;
|
2016-10-20 22:05:03 +02:00
|
|
|
int trail_index = vars_[i_lit.var].current_trail_index;
|
|
|
|
|
int prev_trail_index = trail_index;
|
|
|
|
|
while (true) {
|
|
|
|
|
const TrailEntry& entry = integer_trail_[trail_index];
|
|
|
|
|
if (entry.bound == i_lit.bound) return trail_index;
|
|
|
|
|
if (entry.bound < i_lit.bound) return prev_trail_index;
|
2016-03-16 10:10:38 +01:00
|
|
|
prev_trail_index = trail_index;
|
2016-10-20 22:05:03 +02:00
|
|
|
trail_index = entry.prev_trail_index;
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
bool IntegerTrail::EnqueueAssociatedLiteral(
|
|
|
|
|
Literal literal, IntegerLiteral i_lit,
|
2017-06-01 16:28:29 +02:00
|
|
|
gtl::Span<Literal> literal_reason,
|
|
|
|
|
gtl::Span<IntegerLiteral> integer_reason,
|
2016-10-05 13:53:30 +02:00
|
|
|
BooleanVariable* variable_with_same_reason) {
|
2016-09-22 13:55:16 +02:00
|
|
|
if (!trail_->Assignment().VariableIsAssigned(literal.Variable())) {
|
2016-10-20 22:05:03 +02:00
|
|
|
if (integer_decision_levels_.empty()) {
|
|
|
|
|
trail_->EnqueueWithUnitReason(literal);
|
|
|
|
|
return true;
|
2016-10-05 13:53:30 +02:00
|
|
|
}
|
|
|
|
|
|
2016-10-20 22:05:03 +02:00
|
|
|
if (*variable_with_same_reason != kNoBooleanVariable) {
|
|
|
|
|
trail_->EnqueueWithSameReasonAs(literal, *variable_with_same_reason);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
*variable_with_same_reason = literal.Variable();
|
|
|
|
|
|
|
|
|
|
// Subtle: the reason is the same as i_lit, that we will enqueue if no
|
|
|
|
|
// conflict occur at position integer_trail_.size(), so we just refer to
|
|
|
|
|
// this index here. See EnqueueLiteral().
|
|
|
|
|
const int trail_index = trail_->Index();
|
|
|
|
|
if (trail_index >= boolean_trail_index_to_integer_one_.size()) {
|
|
|
|
|
boolean_trail_index_to_integer_one_.resize(trail_index + 1);
|
|
|
|
|
}
|
|
|
|
|
boolean_trail_index_to_integer_one_[trail_index] = integer_trail_.size();
|
|
|
|
|
trail_->Enqueue(literal, propagator_id_);
|
2016-09-22 13:55:16 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (trail_->Assignment().LiteralIsFalse(literal)) {
|
|
|
|
|
std::vector<Literal>* conflict = trail_->MutableConflict();
|
2017-06-01 16:28:29 +02:00
|
|
|
conflict->assign(literal_reason.begin(), literal_reason.end());
|
2016-10-20 22:05:03 +02:00
|
|
|
|
|
|
|
|
// This is tricky, in some corner cases, the same Enqueue() will call
|
|
|
|
|
// EnqueueAssociatedLiteral() on a literal and its opposite. In this case,
|
|
|
|
|
// we don't want to have this in the conflict.
|
|
|
|
|
const AssignmentInfo& info =
|
|
|
|
|
trail_->Info(trail_->ReferenceVarWithSameReason(literal.Variable()));
|
|
|
|
|
if (info.type != propagator_id_ ||
|
|
|
|
|
info.trail_index >= boolean_trail_index_to_integer_one_.size() ||
|
|
|
|
|
boolean_trail_index_to_integer_one_[info.trail_index] !=
|
|
|
|
|
integer_trail_.size()) {
|
|
|
|
|
conflict->push_back(literal);
|
|
|
|
|
}
|
2016-12-16 11:56:35 +01:00
|
|
|
MergeReasonInto(integer_reason, conflict);
|
2016-09-22 13:55:16 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 22:05:03 +02:00
|
|
|
namespace {
|
|
|
|
|
|
2017-06-01 16:28:29 +02:00
|
|
|
std::string ReasonDebugString(gtl::Span<Literal> literal_reason,
|
|
|
|
|
gtl::Span<IntegerLiteral> integer_reason) {
|
2016-10-20 22:05:03 +02:00
|
|
|
std::string result = "literals:{";
|
2016-12-16 11:56:35 +01:00
|
|
|
for (const Literal l : literal_reason) {
|
2016-10-20 22:05:03 +02:00
|
|
|
if (result.back() != '{') result += ",";
|
|
|
|
|
result += l.DebugString();
|
|
|
|
|
}
|
|
|
|
|
result += "} bounds:{";
|
2016-12-16 11:56:35 +01:00
|
|
|
for (const IntegerLiteral l : integer_reason) {
|
2016-10-20 22:05:03 +02:00
|
|
|
if (result.back() != '{') result += ",";
|
|
|
|
|
result += l.DebugString();
|
|
|
|
|
}
|
|
|
|
|
result += "}";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
std::string IntegerTrail::DebugString() {
|
|
|
|
|
std::string result = "trail:{";
|
|
|
|
|
const int num_vars = vars_.size();
|
|
|
|
|
const int limit =
|
|
|
|
|
std::min(num_vars + 30, static_cast<int>(integer_trail_.size()));
|
|
|
|
|
for (int i = num_vars; i < limit; ++i) {
|
|
|
|
|
if (result.back() != '{') result += ",";
|
|
|
|
|
result +=
|
|
|
|
|
IntegerLiteral::GreaterOrEqual(IntegerVariable(integer_trail_[i].var),
|
|
|
|
|
integer_trail_[i].bound)
|
|
|
|
|
.DebugString();
|
|
|
|
|
}
|
|
|
|
|
if (limit < integer_trail_.size()) {
|
|
|
|
|
result += ", ...";
|
|
|
|
|
}
|
|
|
|
|
result += "}";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-12 13:42:16 +02:00
|
|
|
bool IntegerTrail::Enqueue(IntegerLiteral i_lit,
|
2017-06-01 16:28:29 +02:00
|
|
|
gtl::Span<Literal> literal_reason,
|
|
|
|
|
gtl::Span<IntegerLiteral> integer_reason) {
|
2016-12-16 11:56:35 +01:00
|
|
|
DCHECK(AllLiteralsAreFalse(literal_reason));
|
2017-03-28 16:11:06 +02:00
|
|
|
CHECK(!IsCurrentlyIgnored(i_lit.Var()));
|
2016-12-16 11:56:35 +01:00
|
|
|
|
2016-03-16 10:10:38 +01:00
|
|
|
// Nothing to do if the bound is not better than the current one.
|
2017-03-28 16:11:06 +02:00
|
|
|
// TODO(user): Change this to a CHECK? propagator shouldn't try to push such
|
|
|
|
|
// bound and waste time explaining it.
|
2016-09-12 13:42:16 +02:00
|
|
|
if (i_lit.bound <= vars_[i_lit.var].current_bound) return true;
|
2016-03-16 10:10:38 +01:00
|
|
|
++num_enqueues_;
|
|
|
|
|
|
2016-10-20 22:05:03 +02:00
|
|
|
// This may not indicate an incorectness, but just some propagators that
|
|
|
|
|
// didn't reach a fixed-point at level zero.
|
|
|
|
|
if (DEBUG_MODE && !integer_decision_levels_.empty()) {
|
2017-06-01 16:28:29 +02:00
|
|
|
std::vector<Literal> l(literal_reason.begin(), literal_reason.end());
|
2016-12-16 11:56:35 +01:00
|
|
|
MergeReasonInto(integer_reason, &l);
|
2017-02-06 16:11:43 +01:00
|
|
|
LOG_IF(WARNING, l.empty())
|
2016-10-20 22:05:03 +02:00
|
|
|
<< "Propagating a literal with no reason at a positive level!\n"
|
|
|
|
|
<< "level:" << integer_decision_levels_.size() << " i_lit:" << i_lit
|
2016-12-16 11:56:35 +01:00
|
|
|
<< " " << ReasonDebugString(literal_reason, integer_reason) << "\n"
|
2016-10-20 22:05:03 +02:00
|
|
|
<< DebugString();
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-08 14:58:18 +01:00
|
|
|
const IntegerVariable var(i_lit.var);
|
|
|
|
|
|
|
|
|
|
// If the domain of var is not a single intervals and i_lit.bound fall into a
|
|
|
|
|
// "hole", we increase it to the next possible value.
|
|
|
|
|
{
|
|
|
|
|
int interval_index =
|
|
|
|
|
FindWithDefault(var_to_current_lb_interval_index_, var, -1);
|
|
|
|
|
if (interval_index >= 0) {
|
|
|
|
|
const int end_index = FindOrDie(var_to_end_interval_index_, var);
|
|
|
|
|
while (interval_index < end_index &&
|
|
|
|
|
i_lit.bound > all_intervals_[interval_index].end) {
|
|
|
|
|
++interval_index;
|
|
|
|
|
}
|
|
|
|
|
if (interval_index == end_index) {
|
2016-12-16 11:56:35 +01:00
|
|
|
return ReportConflict(literal_reason, integer_reason);
|
2016-12-08 14:58:18 +01:00
|
|
|
} else {
|
|
|
|
|
var_to_current_lb_interval_index_.Set(var, interval_index);
|
|
|
|
|
i_lit.bound = std::max(
|
|
|
|
|
i_lit.bound, IntegerValue(all_intervals_[interval_index].start));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 13:53:30 +02:00
|
|
|
// For the EnqueueWithSameReasonAs() mechanism.
|
|
|
|
|
BooleanVariable first_propagated_variable = kNoBooleanVariable;
|
|
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
// Deal with fully encoded variable. We want to do that first because this may
|
|
|
|
|
// make the IntegerLiteral bound stronger.
|
2016-11-09 13:23:23 +01:00
|
|
|
const int min_index = FindWithDefault(current_min_, var, -1);
|
|
|
|
|
if (min_index >= 0) {
|
2017-03-28 16:11:06 +02:00
|
|
|
DCHECK(!IsOptional(var))
|
|
|
|
|
<< "Fully encoded optional variable are not yet supported.";
|
|
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
// Recover the current min, and propagate to false all the values that
|
|
|
|
|
// are in [min, i_lit.value). All these literals have the same reason, so
|
|
|
|
|
// we use the "same reason as" mecanism.
|
2016-10-20 22:05:03 +02:00
|
|
|
//
|
|
|
|
|
// TODO(user): We could go even further if the next literals are set to
|
|
|
|
|
// false (but they need to be added to the reason).
|
2016-09-22 13:55:16 +02:00
|
|
|
const auto& encoding = encoder_->FullDomainEncoding(var);
|
|
|
|
|
if (i_lit.bound > encoding[min_index].value) {
|
2016-10-05 13:53:30 +02:00
|
|
|
int i = min_index;
|
2016-09-22 13:55:16 +02:00
|
|
|
for (; i < encoding.size(); ++i) {
|
|
|
|
|
if (i_lit.bound <= encoding[i].value) break;
|
|
|
|
|
const Literal literal = encoding[i].literal.Negated();
|
2016-12-16 11:56:35 +01:00
|
|
|
if (!EnqueueAssociatedLiteral(literal, i_lit, literal_reason,
|
|
|
|
|
integer_reason,
|
2016-10-05 13:53:30 +02:00
|
|
|
&first_propagated_variable)) {
|
2016-09-22 13:55:16 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i == encoding.size()) {
|
|
|
|
|
// Conflict: no possible values left.
|
2016-12-16 11:56:35 +01:00
|
|
|
return ReportConflict(literal_reason, integer_reason);
|
2016-09-22 13:55:16 +02:00
|
|
|
} else {
|
2016-11-09 13:23:23 +01:00
|
|
|
// We have a new min. Note that watched_min_ will be updated on the next
|
|
|
|
|
// call to Propagate() since we just pushed the watched literal if it
|
|
|
|
|
// wasn't already set to false.
|
2016-09-22 13:55:16 +02:00
|
|
|
current_min_.Set(var, i);
|
|
|
|
|
|
|
|
|
|
// Adjust the bound of i_lit !
|
|
|
|
|
CHECK_GE(encoding[i].value, i_lit.bound);
|
|
|
|
|
i_lit.bound = encoding[i].value.value();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the integer variable has an empty domain.
|
2016-09-12 13:42:16 +02:00
|
|
|
if (i_lit.bound > UpperBound(var)) {
|
2017-03-28 16:11:06 +02:00
|
|
|
// We relax the upper bound as much as possible to still have a conflict.
|
|
|
|
|
const auto ub_reason = IntegerLiteral::LowerOrEqual(var, i_lit.bound - 1);
|
|
|
|
|
|
|
|
|
|
if (!IsOptional(var) || trail_->Assignment().LiteralIsFalse(
|
|
|
|
|
Literal(is_ignored_literals_[var]))) {
|
2016-09-12 13:42:16 +02:00
|
|
|
std::vector<Literal>* conflict = trail_->MutableConflict();
|
2017-06-01 16:28:29 +02:00
|
|
|
conflict->assign(literal_reason.begin(), literal_reason.end());
|
2016-09-12 13:42:16 +02:00
|
|
|
if (IsOptional(var)) {
|
2017-03-28 16:11:06 +02:00
|
|
|
conflict->push_back(Literal(is_ignored_literals_[var]));
|
2016-09-12 13:42:16 +02:00
|
|
|
}
|
2016-10-20 22:05:03 +02:00
|
|
|
|
|
|
|
|
// This is the same as:
|
2016-12-16 11:56:35 +01:00
|
|
|
// MergeReasonInto(integer_reason, conflict);
|
2017-03-28 16:11:06 +02:00
|
|
|
// MergeReasonInto({ub_reason)}, conflict);
|
2016-10-20 22:05:03 +02:00
|
|
|
// but with just one call to MergeReasonIntoInternal() for speed. Note
|
|
|
|
|
// that it may also produce a smaller reason overall.
|
|
|
|
|
DCHECK(tmp_queue_.empty());
|
|
|
|
|
const int size = vars_.size();
|
2016-12-16 11:56:35 +01:00
|
|
|
for (const IntegerLiteral& literal : integer_reason) {
|
2016-10-20 22:05:03 +02:00
|
|
|
const int trail_index = FindLowestTrailIndexThatExplainBound(literal);
|
|
|
|
|
if (trail_index >= size) tmp_queue_.push_back(trail_index);
|
|
|
|
|
}
|
|
|
|
|
{
|
2017-03-28 16:11:06 +02:00
|
|
|
const int trail_index = FindLowestTrailIndexThatExplainBound(ub_reason);
|
2016-10-20 22:05:03 +02:00
|
|
|
if (trail_index >= size) tmp_queue_.push_back(trail_index);
|
|
|
|
|
}
|
|
|
|
|
MergeReasonIntoInternal(conflict);
|
2016-09-12 13:42:16 +02:00
|
|
|
return false;
|
|
|
|
|
} else {
|
2017-03-28 16:11:06 +02:00
|
|
|
// Note(user): We never make the bound of an optional literal cross. We
|
|
|
|
|
// used to have a bug where we propagated these bound and their associated
|
|
|
|
|
// literal, and we where reaching a conflict while propagating the
|
|
|
|
|
// associated literal instead of setting is_ignored below to false.
|
|
|
|
|
const Literal is_ignored = Literal(is_ignored_literals_[var]);
|
|
|
|
|
if (integer_decision_levels_.empty()) {
|
|
|
|
|
trail_->EnqueueWithUnitReason(is_ignored);
|
|
|
|
|
} else {
|
|
|
|
|
EnqueueLiteral(is_ignored, literal_reason, integer_reason);
|
|
|
|
|
bounds_reason_buffer_.push_back(ub_reason);
|
2016-09-12 13:42:16 +02:00
|
|
|
}
|
2017-03-28 16:11:06 +02:00
|
|
|
return true;
|
2016-09-12 13:42:16 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 10:10:38 +01:00
|
|
|
// Notify the watchers.
|
2016-09-12 13:42:16 +02:00
|
|
|
for (SparseBitset<IntegerVariable>* bitset : watchers_) {
|
|
|
|
|
bitset->Set(IntegerVariable(i_lit.var));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enqueue the strongest associated Boolean literal implied by this one.
|
|
|
|
|
const LiteralIndex literal_index =
|
|
|
|
|
encoder_->SearchForLiteralAtOrBefore(i_lit);
|
|
|
|
|
if (literal_index != kNoLiteralIndex) {
|
2016-12-16 11:56:35 +01:00
|
|
|
if (!EnqueueAssociatedLiteral(Literal(literal_index), i_lit, literal_reason,
|
|
|
|
|
integer_reason, &first_propagated_variable)) {
|
2016-09-12 13:42:16 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-16 10:10:38 +01:00
|
|
|
|
|
|
|
|
// Special case for level zero.
|
|
|
|
|
if (integer_decision_levels_.empty()) {
|
|
|
|
|
vars_[i_lit.var].current_bound = i_lit.bound;
|
|
|
|
|
integer_trail_[i_lit.var].bound = i_lit.bound;
|
2016-09-12 13:42:16 +02:00
|
|
|
return true;
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
integer_trail_.push_back(
|
|
|
|
|
{/*bound=*/i_lit.bound,
|
|
|
|
|
/*var=*/i_lit.var,
|
|
|
|
|
/*prev_trail_index=*/vars_[i_lit.var].current_trail_index,
|
2017-03-28 16:11:06 +02:00
|
|
|
/*literals_reason_start_index=*/
|
|
|
|
|
static_cast<int32>(literals_reason_buffer_.size()),
|
|
|
|
|
/*dependencies_start_index=*/
|
|
|
|
|
static_cast<int32>(bounds_reason_buffer_.size())});
|
2016-03-16 10:10:38 +01:00
|
|
|
vars_[i_lit.var].current_bound = i_lit.bound;
|
|
|
|
|
vars_[i_lit.var].current_trail_index = integer_trail_.size() - 1;
|
|
|
|
|
|
2016-10-20 22:05:03 +02:00
|
|
|
// Save the reason into our internal buffers.
|
2016-12-16 11:56:35 +01:00
|
|
|
if (!literal_reason.empty()) {
|
2016-10-20 22:05:03 +02:00
|
|
|
literals_reason_buffer_.insert(literals_reason_buffer_.end(),
|
2016-12-16 11:56:35 +01:00
|
|
|
literal_reason.begin(),
|
|
|
|
|
literal_reason.end());
|
2016-10-20 22:05:03 +02:00
|
|
|
}
|
2016-12-16 11:56:35 +01:00
|
|
|
if (!integer_reason.empty()) {
|
|
|
|
|
CHECK_NE(integer_reason[0].var, kNoIntegerVariable);
|
2016-10-20 22:05:03 +02:00
|
|
|
bounds_reason_buffer_.insert(bounds_reason_buffer_.end(),
|
2016-12-16 11:56:35 +01:00
|
|
|
integer_reason.begin(), integer_reason.end());
|
2016-10-20 22:05:03 +02:00
|
|
|
}
|
2016-09-12 13:42:16 +02:00
|
|
|
return true;
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
|
2016-10-20 22:05:03 +02:00
|
|
|
BeginEndWrapper<std::vector<IntegerLiteral>::const_iterator>
|
|
|
|
|
IntegerTrail::Dependencies(int trail_index) const {
|
|
|
|
|
const int start = integer_trail_[trail_index].dependencies_start_index;
|
|
|
|
|
const int end = trail_index + 1 < integer_trail_.size()
|
|
|
|
|
? integer_trail_[trail_index + 1].dependencies_start_index
|
|
|
|
|
: bounds_reason_buffer_.size();
|
|
|
|
|
if (start < end && bounds_reason_buffer_[start].var >= 0) {
|
|
|
|
|
// HACK. This is a critical code, so we reuse the IntegerLiteral.var to
|
|
|
|
|
// store the result of FindLowestTrailIndexThatExplainBound() applied to all
|
|
|
|
|
// the IntegerLiteral.
|
|
|
|
|
//
|
|
|
|
|
// To detect if we already did the computation, we store the negated index.
|
|
|
|
|
// Note that we will redo the computation in the corner case where all the
|
|
|
|
|
// given IntegerLiteral turn out to be assigned at level zero.
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): We could check that the same IntegerVariable never appear
|
|
|
|
|
// twice. And if it does the one with the lowest bound could be removed.
|
|
|
|
|
int out = start;
|
|
|
|
|
const int size = vars_.size();
|
|
|
|
|
for (int i = start; i < end; ++i) {
|
|
|
|
|
const int dep =
|
|
|
|
|
FindLowestTrailIndexThatExplainBound(bounds_reason_buffer_[i]);
|
|
|
|
|
if (dep >= size) bounds_reason_buffer_[out++].var = -dep;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const std::vector<IntegerLiteral>::const_iterator b =
|
|
|
|
|
bounds_reason_buffer_.begin() + start;
|
|
|
|
|
const std::vector<IntegerLiteral>::const_iterator e =
|
|
|
|
|
bounds_reason_buffer_.begin() + end;
|
|
|
|
|
return BeginEndRange(b, e);
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IntegerTrail::AppendLiteralsReason(int trail_index,
|
|
|
|
|
std::vector<Literal>* output) const {
|
|
|
|
|
output->insert(
|
|
|
|
|
output->end(),
|
|
|
|
|
literals_reason_buffer_.begin() +
|
|
|
|
|
integer_trail_[trail_index].literals_reason_start_index,
|
|
|
|
|
trail_index + 1 < integer_trail_.size()
|
|
|
|
|
? literals_reason_buffer_.begin() +
|
|
|
|
|
integer_trail_[trail_index + 1].literals_reason_start_index
|
|
|
|
|
: literals_reason_buffer_.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Literal> IntegerTrail::ReasonFor(IntegerLiteral literal) const {
|
|
|
|
|
std::vector<Literal> reason;
|
|
|
|
|
MergeReasonInto({literal}, &reason);
|
|
|
|
|
return reason;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-16 11:56:35 +01:00
|
|
|
bool IntegerTrail::AllLiteralsAreFalse(
|
2017-06-01 16:28:29 +02:00
|
|
|
gtl::Span<Literal> literals) const {
|
2016-12-16 11:56:35 +01:00
|
|
|
for (const Literal lit : literals) {
|
|
|
|
|
if (!trail_->Assignment().LiteralIsFalse(lit)) return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 10:10:38 +01:00
|
|
|
// TODO(user): If this is called many time on the same variables, it could be
|
|
|
|
|
// made faster by using some caching mecanism.
|
2017-06-01 16:28:29 +02:00
|
|
|
void IntegerTrail::MergeReasonInto(gtl::Span<IntegerLiteral> literals,
|
2016-03-16 10:10:38 +01:00
|
|
|
std::vector<Literal>* output) const {
|
2016-06-02 13:19:10 +02:00
|
|
|
DCHECK(tmp_queue_.empty());
|
2016-03-16 10:10:38 +01:00
|
|
|
const int size = vars_.size();
|
|
|
|
|
for (const IntegerLiteral& literal : literals) {
|
|
|
|
|
const int trail_index = FindLowestTrailIndexThatExplainBound(literal);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
2016-03-16 10:10:38 +01:00
|
|
|
// Any indices lower than that means that there is no reason needed.
|
|
|
|
|
// Note that it is important for size to be signed because of -1 indices.
|
2016-06-02 13:19:10 +02:00
|
|
|
if (trail_index >= size) tmp_queue_.push_back(trail_index);
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
2016-10-20 22:05:03 +02:00
|
|
|
return MergeReasonIntoInternal(output);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-29 14:45:17 +02:00
|
|
|
// This will expand the reason of the IntegerLiteral already in tmp_queue_ until
|
|
|
|
|
// everything is explained in term of Literal.
|
2016-10-20 22:05:03 +02:00
|
|
|
void IntegerTrail::MergeReasonIntoInternal(std::vector<Literal>* output) const {
|
2017-05-29 14:45:17 +02:00
|
|
|
// All relevant trail indices will be >= vars_.size(), so we can safely use
|
|
|
|
|
// zero to means that no literal refering to this variable is in the queue.
|
|
|
|
|
tmp_var_to_trail_index_in_queue_.resize(vars_.size(), 0);
|
|
|
|
|
DCHECK(std::all_of(tmp_var_to_trail_index_in_queue_.begin(),
|
|
|
|
|
tmp_var_to_trail_index_in_queue_.end(),
|
2016-10-20 22:05:03 +02:00
|
|
|
[](int v) { return v == 0; }));
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2017-05-29 14:45:17 +02:00
|
|
|
// During the algorithm execution, all the queue entries that do not match the
|
|
|
|
|
// content of tmp_var_to_trail_index_in_queue_[] will be ignored.
|
|
|
|
|
for (const int trail_index : tmp_queue_) {
|
|
|
|
|
const TrailEntry& entry = integer_trail_[trail_index];
|
|
|
|
|
tmp_var_to_trail_index_in_queue_[entry.var] =
|
|
|
|
|
std::max(tmp_var_to_trail_index_in_queue_[entry.var], trail_index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We manage our heap by hand so that we can range iterate over it above, and
|
|
|
|
|
// this initial heapify is faster.
|
|
|
|
|
std::make_heap(tmp_queue_.begin(), tmp_queue_.end());
|
|
|
|
|
|
|
|
|
|
// We process the entries by highest trail_index first. The content of the
|
|
|
|
|
// queue will always be a valid reason for the literals we already added to
|
|
|
|
|
// the output.
|
|
|
|
|
tmp_to_clear_.clear();
|
2016-06-02 13:19:10 +02:00
|
|
|
while (!tmp_queue_.empty()) {
|
2017-05-29 14:45:17 +02:00
|
|
|
const int trail_index = tmp_queue_.front();
|
2016-06-02 13:19:10 +02:00
|
|
|
const TrailEntry& entry = integer_trail_[trail_index];
|
2017-05-29 14:45:17 +02:00
|
|
|
std::pop_heap(tmp_queue_.begin(), tmp_queue_.end());
|
|
|
|
|
tmp_queue_.pop_back();
|
2016-06-02 13:19:10 +02:00
|
|
|
|
2017-05-29 14:45:17 +02:00
|
|
|
// Skip any stale queue entry. Amongst all the entry refering to a given
|
|
|
|
|
// variable, only the latest added to the queue is valid and we detect it
|
|
|
|
|
// using its trail index.
|
|
|
|
|
if (tmp_var_to_trail_index_in_queue_[entry.var] != trail_index) {
|
2016-06-02 13:19:10 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2017-05-29 14:45:17 +02:00
|
|
|
// If this entry has an associated literal, then we use it as a reason
|
|
|
|
|
// instead of the stored reason. If later this literal needs to be
|
|
|
|
|
// explained, then the associated literal will be expanded with the stored
|
|
|
|
|
// reason.
|
|
|
|
|
{
|
|
|
|
|
const LiteralIndex associated_lit =
|
|
|
|
|
encoder_->GetAssociatedLiteral(IntegerLiteral::GreaterOrEqual(
|
|
|
|
|
IntegerVariable(entry.var), entry.bound));
|
|
|
|
|
if (associated_lit != kNoLiteralIndex) {
|
|
|
|
|
output->push_back(Literal(associated_lit).Negated());
|
|
|
|
|
|
|
|
|
|
// Ignore any entries of the queue refering to this variable and make
|
|
|
|
|
// sure no such entry are added later.
|
|
|
|
|
tmp_to_clear_.push_back(entry.var);
|
|
|
|
|
tmp_var_to_trail_index_in_queue_[entry.var] = kint32max;
|
|
|
|
|
continue;
|
2016-06-02 13:19:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2017-05-29 14:45:17 +02:00
|
|
|
// Process this entry. Note that if any of the next expansion include the
|
|
|
|
|
// variable entry.var in their reason, we must process it again because we
|
|
|
|
|
// cannot easily detect if it was needed to infer the current entry.
|
|
|
|
|
//
|
|
|
|
|
// Important: the queue might already contains entries refering to the same
|
|
|
|
|
// variable. The code act like if we deleted all of them at this point, we
|
|
|
|
|
// just do that lazily. tmp_var_to_trail_index_in_queue_[var] will
|
|
|
|
|
// only refer to newly added entries.
|
2016-03-16 10:10:38 +01:00
|
|
|
AppendLiteralsReason(trail_index, output);
|
2017-05-29 14:45:17 +02:00
|
|
|
tmp_var_to_trail_index_in_queue_[entry.var] = 0;
|
|
|
|
|
|
|
|
|
|
// TODO(user): we could speed up Dependencies() by using the indices stored
|
|
|
|
|
// in tmp_var_to_trail_index_in_queue_ instead of redoing
|
|
|
|
|
// FindLowestTrailIndexThatExplainBound() from the latest trail index.
|
|
|
|
|
bool has_dependency = false;
|
|
|
|
|
for (const IntegerLiteral lit : Dependencies(trail_index)) {
|
|
|
|
|
// Extract the next_trail_index from the returned literal, we can break
|
|
|
|
|
// as soon as we get a negative next_trail_index. See the encoding in
|
|
|
|
|
// Dependencies().
|
|
|
|
|
const int next_trail_index = -lit.var;
|
|
|
|
|
if (next_trail_index < 0) break;
|
|
|
|
|
const TrailEntry& next_entry = integer_trail_[next_trail_index];
|
|
|
|
|
has_dependency = true;
|
|
|
|
|
|
|
|
|
|
// Only add literals that are not "implied" by the ones already present.
|
|
|
|
|
// For instance, do not add (x >= 4) if we already have (x >= 7). This
|
|
|
|
|
// translate into only adding a trail index if it is larger than the one
|
|
|
|
|
// in the queue refering to the same variable.
|
|
|
|
|
if (next_trail_index > tmp_var_to_trail_index_in_queue_[next_entry.var]) {
|
|
|
|
|
tmp_var_to_trail_index_in_queue_[next_entry.var] = next_trail_index;
|
|
|
|
|
tmp_queue_.push_back(next_trail_index);
|
|
|
|
|
std::push_heap(tmp_queue_.begin(), tmp_queue_.end());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Special case for a "leaf", we will never need this variable again.
|
|
|
|
|
if (!has_dependency) {
|
|
|
|
|
tmp_to_clear_.push_back(entry.var);
|
|
|
|
|
tmp_var_to_trail_index_in_queue_[entry.var] = kint32max;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// clean-up.
|
|
|
|
|
for (const int var : tmp_to_clear_) {
|
|
|
|
|
tmp_var_to_trail_index_in_queue_[var] = 0;
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
STLSortAndRemoveDuplicates(output);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-01 16:28:29 +02:00
|
|
|
gtl::Span<Literal> IntegerTrail::Reason(const Trail& trail,
|
|
|
|
|
int trail_index) const {
|
2016-03-16 10:10:38 +01:00
|
|
|
std::vector<Literal>* reason = trail.GetVectorToStoreReason(trail_index);
|
2016-10-20 22:05:03 +02:00
|
|
|
reason->clear();
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2016-10-20 22:05:03 +02:00
|
|
|
const int index = boolean_trail_index_to_integer_one_[trail_index];
|
|
|
|
|
AppendLiteralsReason(index, reason);
|
|
|
|
|
DCHECK(tmp_queue_.empty());
|
|
|
|
|
for (const IntegerLiteral lit : Dependencies(index)) {
|
|
|
|
|
const int next_trail_index = -lit.var;
|
|
|
|
|
if (next_trail_index < 0) break;
|
|
|
|
|
tmp_queue_.push_back(next_trail_index);
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
2016-10-20 22:05:03 +02:00
|
|
|
MergeReasonIntoInternal(reason);
|
2017-06-01 16:28:29 +02:00
|
|
|
return *reason;
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
|
2016-09-22 13:55:16 +02:00
|
|
|
void IntegerTrail::EnqueueLiteral(
|
2017-06-01 16:28:29 +02:00
|
|
|
Literal literal, gtl::Span<Literal> literal_reason,
|
|
|
|
|
gtl::Span<IntegerLiteral> integer_reason) {
|
2016-12-16 11:56:35 +01:00
|
|
|
DCHECK(AllLiteralsAreFalse(literal_reason));
|
2016-10-20 22:05:03 +02:00
|
|
|
if (integer_decision_levels_.empty()) {
|
|
|
|
|
// Level zero. We don't keep any reason.
|
|
|
|
|
trail_->EnqueueWithUnitReason(literal);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
// This may not indicate an incorectness, but just some propagators that
|
|
|
|
|
// didn't reach a fixed-point at level zero.
|
|
|
|
|
if (DEBUG_MODE && !integer_decision_levels_.empty()) {
|
2017-06-01 16:28:29 +02:00
|
|
|
std::vector<Literal> l(literal_reason.begin(), literal_reason.end());
|
2017-03-28 16:11:06 +02:00
|
|
|
MergeReasonInto(integer_reason, &l);
|
|
|
|
|
LOG_IF(WARNING, l.empty())
|
|
|
|
|
<< "Propagating a literal with no reason at a positive level!\n"
|
|
|
|
|
<< "level:" << integer_decision_levels_.size() << " lit:" << literal
|
|
|
|
|
<< " " << ReasonDebugString(literal_reason, integer_reason) << "\n"
|
|
|
|
|
<< DebugString();
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 22:05:03 +02:00
|
|
|
const int trail_index = trail_->Index();
|
|
|
|
|
if (trail_index >= boolean_trail_index_to_integer_one_.size()) {
|
|
|
|
|
boolean_trail_index_to_integer_one_.resize(trail_index + 1);
|
|
|
|
|
}
|
|
|
|
|
boolean_trail_index_to_integer_one_[trail_index] = integer_trail_.size();
|
|
|
|
|
integer_trail_.push_back({/*bound=*/IntegerValue(0), /*var=*/-1,
|
|
|
|
|
/*prev_trail_index=*/-1,
|
2017-03-28 16:11:06 +02:00
|
|
|
/*literals_reason_start_index=*/
|
|
|
|
|
static_cast<int32>(literals_reason_buffer_.size()),
|
|
|
|
|
/*dependencies_start_index=*/
|
|
|
|
|
static_cast<int32>(bounds_reason_buffer_.size())});
|
2016-10-20 22:05:03 +02:00
|
|
|
literals_reason_buffer_.insert(literals_reason_buffer_.end(),
|
|
|
|
|
literal_reason.begin(), literal_reason.end());
|
|
|
|
|
bounds_reason_buffer_.insert(bounds_reason_buffer_.end(),
|
|
|
|
|
integer_reason.begin(), integer_reason.end());
|
|
|
|
|
trail_->Enqueue(literal, propagator_id_);
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
|
2016-11-02 16:04:41 +01:00
|
|
|
GenericLiteralWatcher::GenericLiteralWatcher(
|
|
|
|
|
IntegerTrail* integer_trail, RevRepository<int>* rev_int_repository)
|
|
|
|
|
: SatPropagator("GenericLiteralWatcher"),
|
|
|
|
|
integer_trail_(integer_trail),
|
|
|
|
|
rev_int_repository_(rev_int_repository) {
|
2016-03-16 10:10:38 +01:00
|
|
|
integer_trail_->RegisterWatcher(&modified_vars_);
|
2017-02-07 17:43:43 +01:00
|
|
|
queue_by_priority_.resize(2); // Because default priority is 1.
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
|
2016-11-04 09:52:55 +01:00
|
|
|
void GenericLiteralWatcher::UpdateCallingNeeds(Trail* trail) {
|
|
|
|
|
// Process any new Literal on the trail.
|
|
|
|
|
while (propagation_trail_index_ < trail->Index()) {
|
|
|
|
|
const Literal literal = (*trail)[propagation_trail_index_++];
|
2016-11-29 13:42:24 +01:00
|
|
|
if (literal.Index() >= literal_to_watcher_.size()) continue;
|
|
|
|
|
for (const auto entry : literal_to_watcher_[literal.Index()]) {
|
|
|
|
|
if (!in_queue_[entry.id]) {
|
|
|
|
|
in_queue_[entry.id] = true;
|
2017-02-07 17:43:43 +01:00
|
|
|
queue_by_priority_[id_to_priority_[entry.id]].push_back(entry.id);
|
2016-11-29 13:42:24 +01:00
|
|
|
}
|
|
|
|
|
if (entry.watch_index >= 0) {
|
|
|
|
|
id_to_watch_indices_[entry.id].push_back(entry.watch_index);
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-04 09:52:55 +01:00
|
|
|
// Process the newly changed variables lower bounds.
|
|
|
|
|
for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
|
2016-11-29 13:42:24 +01:00
|
|
|
if (var.value() >= var_to_watcher_.size()) continue;
|
|
|
|
|
for (const auto entry : var_to_watcher_[var]) {
|
|
|
|
|
if (!in_queue_[entry.id]) {
|
|
|
|
|
in_queue_[entry.id] = true;
|
2017-02-07 17:43:43 +01:00
|
|
|
queue_by_priority_[id_to_priority_[entry.id]].push_back(entry.id);
|
2016-11-29 13:42:24 +01:00
|
|
|
}
|
|
|
|
|
if (entry.watch_index >= 0) {
|
|
|
|
|
id_to_watch_indices_[entry.id].push_back(entry.watch_index);
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-11-04 09:52:55 +01:00
|
|
|
modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
|
|
|
|
|
}
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2016-11-04 09:52:55 +01:00
|
|
|
bool GenericLiteralWatcher::Propagate(Trail* trail) {
|
2016-11-02 16:04:41 +01:00
|
|
|
const int level = trail->CurrentDecisionLevel();
|
|
|
|
|
rev_int_repository_->SetLevel(level);
|
2016-11-04 09:52:55 +01:00
|
|
|
UpdateCallingNeeds(trail);
|
2017-02-07 17:43:43 +01:00
|
|
|
|
|
|
|
|
// Note that the priority may be set to -1 inside the loop in order to restart
|
|
|
|
|
// at zero.
|
|
|
|
|
for (int priority = 0; priority < queue_by_priority_.size(); ++priority) {
|
|
|
|
|
std::deque<int>& queue = queue_by_priority_[priority];
|
|
|
|
|
while (!queue.empty()) {
|
|
|
|
|
const int id = queue.front();
|
|
|
|
|
queue.pop_front();
|
|
|
|
|
|
|
|
|
|
// Before we propagate, make sure any reversible structure are up to date.
|
|
|
|
|
// Note that we never do anything expensive more than once per level.
|
|
|
|
|
{
|
|
|
|
|
const int low = id_to_greatest_common_level_since_last_call_[id];
|
|
|
|
|
const int high = id_to_level_at_last_call_[id];
|
|
|
|
|
if (low < high || level > low) { // Equivalent to not all equal.
|
|
|
|
|
id_to_level_at_last_call_[id] = level;
|
|
|
|
|
id_to_greatest_common_level_since_last_call_[id] = level;
|
|
|
|
|
for (ReversibleInterface* rev : id_to_reversible_classes_[id]) {
|
|
|
|
|
if (low < high) rev->SetLevel(low);
|
|
|
|
|
if (level > low) rev->SetLevel(level);
|
|
|
|
|
}
|
|
|
|
|
for (int* rev_int : id_to_reversible_ints_[id]) {
|
|
|
|
|
rev_int_repository_->SaveState(rev_int);
|
|
|
|
|
}
|
2016-11-02 16:04:41 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-07 17:43:43 +01:00
|
|
|
// This is needed to detect if the propagator propagated anything or not.
|
|
|
|
|
const int64 old_integer_timestamp = integer_trail_->num_enqueues();
|
|
|
|
|
const int64 old_boolean_timestamp = trail->Index();
|
|
|
|
|
|
|
|
|
|
// TODO(user): Maybe just provide one function Propagate(watch_indices) ?
|
|
|
|
|
std::vector<int>& watch_indices_ref = id_to_watch_indices_[id];
|
|
|
|
|
const bool result =
|
|
|
|
|
watch_indices_ref.empty()
|
|
|
|
|
? watchers_[id]->Propagate()
|
|
|
|
|
: watchers_[id]->IncrementalPropagate(watch_indices_ref);
|
|
|
|
|
if (!result) {
|
|
|
|
|
watch_indices_ref.clear();
|
|
|
|
|
in_queue_[id] = false;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the propagation queue. At this point, the propagator has been
|
|
|
|
|
// removed from the queue but in_queue_ is still true.
|
|
|
|
|
if (id_to_idempotence_[id]) {
|
|
|
|
|
// If the propagator is assumed to be idempotent, then we set in_queue_
|
|
|
|
|
// to false after UpdateCallingNeeds() so this later function will never
|
|
|
|
|
// add it back.
|
|
|
|
|
UpdateCallingNeeds(trail);
|
|
|
|
|
watch_indices_ref.clear();
|
|
|
|
|
in_queue_[id] = false;
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, we set in_queue_ to false first so that
|
|
|
|
|
// UpdateCallingNeeds() may add it back if the propagator modified any
|
|
|
|
|
// of its watched variables.
|
|
|
|
|
watch_indices_ref.clear();
|
|
|
|
|
in_queue_[id] = false;
|
|
|
|
|
UpdateCallingNeeds(trail);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the propagator pushed an integer bound, we revert to priority = 0.
|
|
|
|
|
if (integer_trail_->num_enqueues() > old_integer_timestamp) {
|
|
|
|
|
priority = -1; // Because of the ++priority in the for loop.
|
|
|
|
|
}
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2017-02-07 17:43:43 +01:00
|
|
|
// If the propagator pushed a literal, we have two options.
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): expose the parameter. Early experiments are counter
|
|
|
|
|
// intuitive and seems to indicate that it is better not to re-run the SAT
|
|
|
|
|
// propagators each time we push a literal.
|
|
|
|
|
if (trail->Index() > old_boolean_timestamp) {
|
|
|
|
|
const bool run_sat_propagators_at_higher_priority = false;
|
|
|
|
|
if (run_sat_propagators_at_higher_priority) {
|
|
|
|
|
// We exit in order to rerun all SAT only propagators first. Note that
|
|
|
|
|
// since a literal was pushed we are guaranteed to be called again,
|
|
|
|
|
// and we will resume from priority 0.
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
priority = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GenericLiteralWatcher::Untrail(const Trail& trail, int trail_index) {
|
2016-11-02 16:04:41 +01:00
|
|
|
if (propagation_trail_index_ <= trail_index) {
|
|
|
|
|
// Nothing to do since we found a conflict before Propagate() was called.
|
|
|
|
|
CHECK_EQ(propagation_trail_index_, trail_index);
|
|
|
|
|
return;
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
2016-11-02 16:04:41 +01:00
|
|
|
|
2016-11-29 13:42:24 +01:00
|
|
|
// We need to clear the watch indices on untrail.
|
2017-02-07 17:43:43 +01:00
|
|
|
for (std::deque<int>& queue : queue_by_priority_) {
|
|
|
|
|
for (const int id : queue) {
|
|
|
|
|
id_to_watch_indices_[id].clear();
|
|
|
|
|
}
|
|
|
|
|
queue.clear();
|
2016-11-29 13:42:24 +01:00
|
|
|
}
|
|
|
|
|
|
2016-11-02 16:04:41 +01:00
|
|
|
// This means that we already propagated all there is to propagate
|
|
|
|
|
// at the level trail_index, so we can safely clear modified_vars_ in case
|
|
|
|
|
// it wasn't already done.
|
2016-03-16 10:10:38 +01:00
|
|
|
propagation_trail_index_ = trail_index;
|
2016-11-02 16:04:41 +01:00
|
|
|
modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
|
|
|
|
|
in_queue_.assign(watchers_.size(), false);
|
|
|
|
|
|
|
|
|
|
const int level = trail.CurrentDecisionLevel();
|
|
|
|
|
rev_int_repository_->SetLevel(level);
|
|
|
|
|
for (int& ref : id_to_greatest_common_level_since_last_call_) {
|
|
|
|
|
ref = std::min(ref, level);
|
|
|
|
|
}
|
2016-03-16 10:10:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Registers a propagator and returns its unique ids.
|
|
|
|
|
int GenericLiteralWatcher::Register(PropagatorInterface* propagator) {
|
|
|
|
|
const int id = watchers_.size();
|
|
|
|
|
watchers_.push_back(propagator);
|
2016-11-02 16:04:41 +01:00
|
|
|
id_to_level_at_last_call_.push_back(0);
|
|
|
|
|
id_to_greatest_common_level_since_last_call_.push_back(0);
|
|
|
|
|
id_to_reversible_classes_.push_back(std::vector<ReversibleInterface*>());
|
|
|
|
|
id_to_reversible_ints_.push_back(std::vector<int*>());
|
2016-11-29 13:42:24 +01:00
|
|
|
id_to_watch_indices_.push_back(std::vector<int>());
|
2017-02-07 17:43:43 +01:00
|
|
|
id_to_priority_.push_back(1);
|
|
|
|
|
id_to_idempotence_.push_back(true);
|
2016-03-16 10:10:38 +01:00
|
|
|
|
2016-11-29 13:42:24 +01:00
|
|
|
// Call this propagator at least once the next time Propagate() is called.
|
2017-02-07 17:43:43 +01:00
|
|
|
//
|
|
|
|
|
// TODO(user): This initial propagation does not respect any later priority
|
|
|
|
|
// settings. Fix this. Maybe we should force users to pass the priority at
|
|
|
|
|
// registration. For now I didn't want to change the interface because there
|
|
|
|
|
// are plans to implement a kind of "dynamic" priority, and if it works we may
|
|
|
|
|
// want to get rid of this altogether.
|
2016-09-12 13:42:16 +02:00
|
|
|
in_queue_.push_back(true);
|
2017-02-07 17:43:43 +01:00
|
|
|
queue_by_priority_[1].push_back(id);
|
2016-03-16 10:10:38 +01:00
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-07 17:43:43 +01:00
|
|
|
void GenericLiteralWatcher::SetPropagatorPriority(int id, int priority) {
|
|
|
|
|
id_to_priority_[id] = priority;
|
|
|
|
|
if (priority >= queue_by_priority_.size()) {
|
|
|
|
|
queue_by_priority_.resize(priority + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GenericLiteralWatcher::NotifyThatPropagatorMayNotReachFixedPointInOnePass(
|
|
|
|
|
int id) {
|
|
|
|
|
id_to_idempotence_[id] = false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-02 16:04:41 +01:00
|
|
|
void GenericLiteralWatcher::RegisterReversibleClass(int id,
|
|
|
|
|
ReversibleInterface* rev) {
|
|
|
|
|
id_to_reversible_classes_[id].push_back(rev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GenericLiteralWatcher::RegisterReversibleInt(int id, int* rev) {
|
|
|
|
|
id_to_reversible_ints_[id].push_back(rev);
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
// TODO(user): the complexity of this heuristic and the one below is ok when
|
|
|
|
|
// use_fixed_search() is false because it is not executed often, however, we do
|
|
|
|
|
// a linear scan for each search decision when use_fixed_search() is true, which
|
|
|
|
|
// seems bad. Improve.
|
|
|
|
|
std::function<LiteralIndex()> FirstUnassignedVarAtItsMinHeuristic(
|
|
|
|
|
const std::vector<IntegerVariable>& vars, Model* model) {
|
2016-10-05 13:53:30 +02:00
|
|
|
IntegerEncoder* const integer_encoder = model->GetOrCreate<IntegerEncoder>();
|
|
|
|
|
IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
|
2017-03-28 16:11:06 +02:00
|
|
|
return [integer_encoder, integer_trail, /*copy*/ vars]() {
|
|
|
|
|
for (const IntegerVariable var : vars) {
|
|
|
|
|
// Note that there is no point trying to fix a currently ignored variable.
|
|
|
|
|
if (integer_trail->IsCurrentlyIgnored(var)) continue;
|
|
|
|
|
const IntegerValue lb = integer_trail->LowerBound(var);
|
|
|
|
|
if (lb < integer_trail->UpperBound(var)) {
|
|
|
|
|
return integer_encoder
|
|
|
|
|
->GetOrCreateAssociatedLiteral(
|
|
|
|
|
IntegerLiteral::LowerOrEqual(var, lb))
|
|
|
|
|
.Index();
|
|
|
|
|
}
|
2016-10-05 13:53:30 +02:00
|
|
|
}
|
2017-04-18 00:08:19 +02:00
|
|
|
return LiteralIndex(kNoLiteralIndex);
|
2017-03-28 16:11:06 +02:00
|
|
|
};
|
|
|
|
|
}
|
2016-10-05 13:53:30 +02:00
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
std::function<LiteralIndex()> UnassignedVarWithLowestMinAtItsMinHeuristic(
|
|
|
|
|
const std::vector<IntegerVariable>& vars, Model* model) {
|
|
|
|
|
IntegerEncoder* const integer_encoder = model->GetOrCreate<IntegerEncoder>();
|
|
|
|
|
IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
|
|
|
|
|
return [integer_encoder, integer_trail, /*copy */ vars]() {
|
2016-10-05 13:53:30 +02:00
|
|
|
IntegerVariable candidate = kNoIntegerVariable;
|
2017-03-28 16:11:06 +02:00
|
|
|
IntegerValue candidate_lb;
|
|
|
|
|
for (const IntegerVariable var : vars) {
|
|
|
|
|
if (integer_trail->IsCurrentlyIgnored(var)) continue;
|
|
|
|
|
const IntegerValue lb = integer_trail->LowerBound(var);
|
|
|
|
|
if (lb < integer_trail->UpperBound(var) &&
|
|
|
|
|
(candidate == kNoIntegerVariable || lb < candidate_lb)) {
|
|
|
|
|
candidate = var;
|
|
|
|
|
candidate_lb = lb;
|
2016-10-05 13:53:30 +02:00
|
|
|
}
|
|
|
|
|
}
|
2017-04-19 16:20:56 +02:00
|
|
|
// Fix compilation error on visual studio 2013.
|
2017-04-18 00:08:19 +02:00
|
|
|
if (candidate == kNoIntegerVariable) return LiteralIndex(kNoLiteralIndex);
|
2017-03-28 16:11:06 +02:00
|
|
|
return integer_encoder
|
|
|
|
|
->GetOrCreateAssociatedLiteral(
|
|
|
|
|
IntegerLiteral::LowerOrEqual(candidate, candidate_lb))
|
|
|
|
|
.Index();
|
|
|
|
|
};
|
|
|
|
|
}
|
2016-10-05 13:53:30 +02:00
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
SatSolver::Status SolveIntegerProblemWithLazyEncoding(
|
|
|
|
|
const std::vector<Literal>& assumptions,
|
|
|
|
|
const std::function<LiteralIndex()>& next_decision, Model* model) {
|
|
|
|
|
// TODO(user): Improve the time-limit situation, especially when
|
|
|
|
|
// use_fixed_search() is true where the deterministic time is not updated.
|
|
|
|
|
TimeLimit* time_limit = model->Mutable<TimeLimit>();
|
|
|
|
|
if (time_limit == nullptr) {
|
|
|
|
|
model->SetSingleton(TimeLimit::Infinite());
|
|
|
|
|
time_limit = model->Mutable<TimeLimit>();
|
|
|
|
|
}
|
|
|
|
|
SatSolver* const solver = model->GetOrCreate<SatSolver>();
|
2017-05-16 10:43:07 +02:00
|
|
|
if (solver->IsModelUnsat()) return SatSolver::MODEL_UNSAT;
|
|
|
|
|
solver->Backtrack(0);
|
|
|
|
|
solver->SetAssumptionLevel(0);
|
|
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
const SatParameters parameters = solver->parameters();
|
|
|
|
|
if (parameters.use_fixed_search()) {
|
|
|
|
|
CHECK(assumptions.empty()) << "Not supported yet";
|
|
|
|
|
// Note that it is important to do the level-zero propagation if it wasn't
|
|
|
|
|
// already done because EnqueueDecisionAndBackjumpOnConflict() assumes that
|
|
|
|
|
// the solver is in a "propagated" state.
|
|
|
|
|
if (!solver->Propagate()) return SatSolver::MODEL_UNSAT;
|
|
|
|
|
}
|
|
|
|
|
while (!time_limit->LimitReached()) {
|
|
|
|
|
// If we are not in fixed search, let the SAT solver do a full search to
|
|
|
|
|
// instanciate all the already created Booleans.
|
|
|
|
|
if (!parameters.use_fixed_search()) {
|
|
|
|
|
if (assumptions.empty()) {
|
|
|
|
|
const SatSolver::Status status = solver->SolveWithTimeLimit(time_limit);
|
|
|
|
|
if (status != SatSolver::MODEL_SAT) return status;
|
|
|
|
|
} else {
|
|
|
|
|
// TODO(user): We actually don't want to reset the solver from one
|
|
|
|
|
// loop to the next as it is less efficient.
|
|
|
|
|
const SatSolver::Status status =
|
|
|
|
|
solver->ResetAndSolveWithGivenAssumptions(assumptions, time_limit);
|
|
|
|
|
if (status != SatSolver::MODEL_SAT) return status;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Look for the "best" non-instanciated integer variable.
|
|
|
|
|
// If all variables are instanciated, we have a solution.
|
|
|
|
|
const LiteralIndex decision =
|
|
|
|
|
next_decision == nullptr ? kNoLiteralIndex : next_decision();
|
|
|
|
|
if (decision == kNoLiteralIndex) return SatSolver::MODEL_SAT;
|
2017-01-18 10:50:37 +01:00
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
// Always try to Enqueue this literal as the next decision so we bypass
|
|
|
|
|
// any default polarity heuristic the solver may use on this literal.
|
|
|
|
|
solver->EnqueueDecisionAndBackjumpOnConflict(Literal(decision));
|
|
|
|
|
if (solver->IsModelUnsat()) return SatSolver::MODEL_UNSAT;
|
2016-10-05 13:53:30 +02:00
|
|
|
}
|
2017-03-28 16:11:06 +02:00
|
|
|
return SatSolver::Status::LIMIT_REACHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shortcut when there is no assumption, and we consider all variables in
|
|
|
|
|
// order for the search decision.
|
|
|
|
|
SatSolver::Status SolveIntegerProblemWithLazyEncoding(Model* model) {
|
|
|
|
|
const IntegerVariable num_vars =
|
|
|
|
|
model->GetOrCreate<IntegerTrail>()->NumIntegerVariables();
|
|
|
|
|
std::vector<IntegerVariable> all_variables;
|
|
|
|
|
for (IntegerVariable var(0); var < num_vars; ++var) {
|
|
|
|
|
all_variables.push_back(var);
|
|
|
|
|
}
|
|
|
|
|
return SolveIntegerProblemWithLazyEncoding(
|
|
|
|
|
{}, FirstUnassignedVarAtItsMinHeuristic(all_variables, model), model);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is really close to ExcludeCurrentSolutionAndBacktrack().
|
|
|
|
|
std::function<void(Model*)>
|
|
|
|
|
ExcludeCurrentSolutionWithoutIgnoredVariableAndBacktrack() {
|
|
|
|
|
return [=](Model* model) {
|
|
|
|
|
SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
|
|
|
|
|
IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
|
|
|
|
|
IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
|
|
|
|
|
|
|
|
|
|
const int current_level = sat_solver->CurrentDecisionLevel();
|
|
|
|
|
std::vector<Literal> clause_to_exclude_solution;
|
|
|
|
|
clause_to_exclude_solution.reserve(current_level);
|
|
|
|
|
for (int i = 0; i < current_level; ++i) {
|
|
|
|
|
bool include_decision = true;
|
|
|
|
|
const Literal decision = sat_solver->Decisions()[i].literal;
|
|
|
|
|
|
|
|
|
|
// Tests if this decision is associated to a bound of an ignored variable
|
|
|
|
|
// in the current assignment.
|
|
|
|
|
const InlinedIntegerLiteralVector& associated_literals =
|
|
|
|
|
encoder->GetIntegerLiterals(decision);
|
|
|
|
|
for (const IntegerLiteral bound : associated_literals) {
|
|
|
|
|
if (integer_trail->IsCurrentlyIgnored(bound.Var())) {
|
|
|
|
|
// In this case we replace the decision (which is a bound on an
|
|
|
|
|
// ignored variable) with the fact that the integer variable was
|
|
|
|
|
// ignored. This works because the only impact a bound of an ignored
|
|
|
|
|
// variable can have on the rest of the model is through the
|
|
|
|
|
// is_ignored literal.
|
|
|
|
|
clause_to_exclude_solution.push_back(
|
|
|
|
|
integer_trail->IsIgnoredLiteral(bound.Var()).Negated());
|
|
|
|
|
include_decision = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (include_decision) {
|
|
|
|
|
clause_to_exclude_solution.push_back(decision.Negated());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note that it is okay to add duplicates literals in ClauseConstraint(),
|
|
|
|
|
// the clause will be preprocessed correctly.
|
|
|
|
|
sat_solver->Backtrack(0);
|
|
|
|
|
model->Add(ClauseConstraint(clause_to_exclude_solution));
|
|
|
|
|
};
|
2016-10-05 13:53:30 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-16 10:10:38 +01:00
|
|
|
} // namespace sat
|
|
|
|
|
} // namespace operations_research
|