// 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. #include "sat/integer.h" #include "base/stl_util.h" namespace operations_research { namespace sat { bool IntegerTrail::Propagate(Trail* trail) { propagation_trail_index_ = trail->Index(); // 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()); } return true; } void IntegerTrail::Untrail(const Trail& trail, int literal_trail_index) { 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]; 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); dependencies_buffer_.resize(integer_trail_[target].dependencies_start_index); integer_trail_.resize(target); } IntegerVariable IntegerTrail::AddIntegerVariable(int lower_bound, int upper_bound) { CHECK(integer_decision_levels_.empty()); CHECK_EQ(vars_.size(), integer_trail_.size()); const IntegerVariable i(vars_.size() / 2); CHECK_EQ(LbVarOf(i).value(), vars_.size()); vars_.push_back({lower_bound, static_cast(integer_trail_.size())}); integer_trail_.push_back({lower_bound, LbVarOf(i).value()}); CHECK_EQ(MinusUbVarOf(i).value(), vars_.size()); vars_.push_back({-upper_bound, static_cast(integer_trail_.size())}); integer_trail_.push_back({-upper_bound, MinusUbVarOf(i).value()}); for (SparseBitset* w : watchers_) { w->Resize(LbVar(NumLbVars())); } return i; } int IntegerTrail::FindLowestTrailIndexThatExplainBound( IntegerLiteral i_lit) const { CHECK_LE(i_lit.bound, vars_[i_lit.var].current_bound); if (i_lit.bound <= LevelZeroBound(i_lit.var)) return -1; int prev_trail_index = vars_[i_lit.var].current_trail_index; int trail_index = prev_trail_index; while (i_lit.bound <= integer_trail_[trail_index].bound) { prev_trail_index = trail_index; trail_index = integer_trail_[trail_index].prev_trail_index; CHECK_GE(trail_index, 0); } return prev_trail_index; } void IntegerTrail::Enqueue(IntegerLiteral i_lit, const std::vector& literals_reason, const std::vector& bounds_reason) { // Nothing to do if the bound is not better than the current one. if (i_lit.bound <= vars_[i_lit.var].current_bound) return; ++num_enqueues_; // Notify the watchers. for (SparseBitset* bitset : watchers_) bitset->Set(LbVar(i_lit.var)); // 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; return; } integer_trail_.push_back( {/*bound=*/i_lit.bound, /*var=*/i_lit.var, /*prev_trail_index=*/vars_[i_lit.var].current_trail_index, /*literals_reason_start_index=*/static_cast( literals_reason_buffer_.size()), /*dependencies_start_index=*/static_cast( dependencies_buffer_.size())}); vars_[i_lit.var].current_bound = i_lit.bound; vars_[i_lit.var].current_trail_index = integer_trail_.size() - 1; // Copy literals_reason into our internal buffer. literals_reason_buffer_.insert(literals_reason_buffer_.end(), literals_reason.begin(), literals_reason.end()); // Convert each IntegerLiteral reason to the index of an entry in the integer // trail. // TODO(user): Do that lazily when the lazy reason are implemented. for (IntegerLiteral i_lit : bounds_reason) { const int reason_tail_index = FindLowestTrailIndexThatExplainBound(i_lit); if (reason_tail_index != -1) { dependencies_buffer_.push_back(reason_tail_index); } } } BeginEndWrapper::const_iterator> IntegerTrail::Dependencies( int trail_index) const { return BeginEndRange( dependencies_buffer_.begin() + integer_trail_[trail_index].dependencies_start_index, trail_index + 1 < integer_trail_.size() ? dependencies_buffer_.begin() + integer_trail_[trail_index + 1].dependencies_start_index : dependencies_buffer_.end()); } void IntegerTrail::AppendLiteralsReason(int trail_index, std::vector* 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 IntegerTrail::ReasonFor(IntegerLiteral literal) const { std::vector reason; MergeReasonInto({literal}, &reason); return reason; } // TODO(user): If this is called many time on the same variables, it could be // made faster by using some caching mecanism. void IntegerTrail::MergeReasonInto(const std::vector& literals, std::vector* output) const { DCHECK(tmp_pq_.empty()); const int size = vars_.size(); for (const IntegerLiteral& literal : literals) { const int trail_index = FindLowestTrailIndexThatExplainBound(literal); // 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. if (trail_index >= size) tmp_pq_.push(trail_index); } // TODO(user): Think about how to minimize the produced reason. It is possible // that literal like x >= 2 and x >= 4 both appear in the same reason, and the // first one may be removable in some cases (provided that we don't need x >= // 2 to proove x >= 4). tmp_seen_.assign(integer_trail_.size(), false); // It is important to process the trail indices in decreasing order, it is // why tmp_pq_ is a priority queue. while (!tmp_pq_.empty()) { const int trail_index = tmp_pq_.top(); tmp_pq_.pop(); if (tmp_seen_[trail_index]) continue; tmp_seen_[trail_index] = true; AppendLiteralsReason(trail_index, output); for (const int next_trail_index : Dependencies(trail_index)) { tmp_pq_.push(next_trail_index); } } STLSortAndRemoveDuplicates(output); } bool IntegerTrail::DomainIsEmpty(IntegerVariable i, Trail* trail) const { if (LowerBound(i) <= UpperBound(i)) return false; std::vector* conflict = trail->MutableConflict(); conflict->clear(); // TODO(user): Avoid allocating memory here? MergeReasonInto({LowerBoundAsLiteral(i), UpperBoundAsLiteral(i)}, conflict); return true; } ClauseRef IntegerTrail::Reason(const Trail& trail, int trail_index) const { std::vector* reason = trail.GetVectorToStoreReason(trail_index); *reason = literal_reasons_[trail_index]; MergeReasonInto(integer_reasons_[trail_index], reason); return ClauseRef(*reason); } void IntegerTrail::EnqueueLiteral(Literal literal, std::vector** literal_reason, std::vector** integer_reason, Trail* trail) { const int trail_index = trail->Index(); if (trail_index >= literal_reasons_.size()) { literal_reasons_.resize(trail_index + 1); integer_reasons_.resize(trail_index + 1); } literal_reasons_[trail_index].clear(); integer_reasons_[trail_index].clear(); if (literal_reason != nullptr) { *literal_reason = &literal_reasons_[trail_index]; } if (integer_reason != nullptr) { *integer_reason = &integer_reasons_[trail_index]; } trail->Enqueue(literal, propagator_id_); } void IntegerTrail::EnqueueLiteral(Literal literal, const std::vector& literal_reason, const std::vector& integer_reason, Trail* trail) { std::vector* literal_reason_ptr; std::vector* integer_reason_ptr; EnqueueLiteral(literal, &literal_reason_ptr, &integer_reason_ptr, trail); *literal_reason_ptr = literal_reason; *integer_reason_ptr = integer_reason; } GenericLiteralWatcher::GenericLiteralWatcher(IntegerTrail* integer_trail) : Propagator("GenericLiteralWatcher"), integer_trail_(integer_trail) { integer_trail_->RegisterWatcher(&modified_vars_); } void GenericLiteralWatcher::UpdateCallingNeeds() { // Process the newly changed LbVars. for (const LbVar var : modified_vars_.PositionsSetAtLeastOnce()) { if (var.value() >= lb_var_to_watcher_ids_.size()) continue; for (const int id : lb_var_to_watcher_ids_[var]) { if (!in_queue_[id]) { in_queue_[id] = true; queue_.push_back(id); } } } const LbVar num_lb_vars(integer_trail_->NumLbVars()); modified_vars_.ClearAndResize(num_lb_vars); } bool GenericLiteralWatcher::Propagate(Trail* trail) { while (propagation_trail_index_ < trail->Index()) { const Literal literal = (*trail)[propagation_trail_index_++]; if (literal.Index() >= literal_to_watcher_ids_.size()) continue; for (const int id : literal_to_watcher_ids_[literal.Index()]) { if (!in_queue_[id]) { in_queue_[id] = true; queue_.push_back(id); } } } UpdateCallingNeeds(); while (!queue_.empty()) { const int id = queue_.front(); queue_.pop_front(); if (!watchers_[id]->Propagate(trail)) { in_queue_[id] = false; return false; } UpdateCallingNeeds(); // We mark the node afterwards because we assume that the Propagate() method // is idempotent and never need to be called twice in a row. If some // propagator don't have this property, we could add an option to call them // again until nothing changes. in_queue_[id] = false; } return true; } void GenericLiteralWatcher::Untrail(const Trail& trail, int trail_index) { if (propagation_trail_index_ > trail_index) { // 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. modified_vars_.ClearAndResize(LbVar(integer_trail_->NumLbVars())); in_queue_.assign(watchers_.size(), false); queue_.clear(); } propagation_trail_index_ = trail_index; } // Registers a propagator and returns its unique ids. int GenericLiteralWatcher::Register(PropagatorInterface* propagator) { const int id = watchers_.size(); watchers_.push_back(propagator); // Initially call everything. in_queue_.resize(watchers_.size(), false); return id; } } // namespace sat } // namespace operations_research