OR-Tools  9.1
implied_bounds.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17
18#include "ortools/sat/integer.h"
19
20namespace operations_research {
21namespace sat {
22
23// Just display some global statistics on destruction.
25 VLOG(1) << num_deductions_ << " enqueued deductions.";
26 VLOG(1) << bounds_.size() << " implied bounds stored.";
27 VLOG(1) << num_enqueued_in_var_to_bounds_
28 << " implied bounds with view stored.";
29}
30
32 if (!parameters_.use_implied_bounds()) return;
33 const IntegerVariable var = integer_literal.var;
34
35 // Update our local level-zero bound.
36 if (var >= level_zero_lower_bounds_.size()) {
37 level_zero_lower_bounds_.resize(var.value() + 1, kMinIntegerValue);
38 new_level_zero_bounds_.Resize(var + 1);
39 }
40 level_zero_lower_bounds_[var] = std::max(
41 level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
42
43 // Ignore any Add() with a bound worse than the level zero one.
44 // TODO(user): Check that this never happen? it shouldn't.
45 if (integer_literal.bound <= level_zero_lower_bounds_[var]) {
46 return;
47 }
48
49 // We skip any IntegerLiteral referring to a variable with only two
50 // consecutive possible values. This is because, once shifted this will
51 // already be a variable in [0, 1] so we shouldn't gain much by substituing
52 // it.
53 if (integer_trail_->LevelZeroLowerBound(var) + 1 >=
54 integer_trail_->LevelZeroUpperBound(var)) {
55 return;
56 }
57
58 // Add or update the current bound.
59 const auto key = std::make_pair(literal.Index(), var);
60 auto insert_result = bounds_.insert({key, integer_literal.bound});
61 if (!insert_result.second) {
62 if (insert_result.first->second < integer_literal.bound) {
63 insert_result.first->second = integer_literal.bound;
64 } else {
65 // No new info.
66 return;
67 }
68 }
69
70 // Checks if the variable is now fixed.
71 if (integer_trail_->LevelZeroUpperBound(var) == integer_literal.bound) {
73 } else {
74 const auto it =
75 bounds_.find(std::make_pair(literal.Index(), NegationOf(var)));
76 if (it != bounds_.end() && it->second == -integer_literal.bound) {
78 }
79 }
80
81 // Check if we have any deduction. Since at least one of (literal,
82 // literal.Negated()) must be true, we can take the min bound as valid at
83 // level zero.
84 //
85 // TODO(user): Like in probing, we can also create hole in the domain if there
86 // is some implied bounds for (literal.NegatedIndex, NegagtionOf(var)) that
87 // crosses integer_literal.bound.
88 const auto it = bounds_.find(std::make_pair(literal.NegatedIndex(), var));
89 if (it != bounds_.end()) {
90 if (it->second <= level_zero_lower_bounds_[var]) {
91 // The other bounds is worse than the new level-zero bound which can
92 // happen because of lazy update, so here we just remove it.
93 bounds_.erase(it);
94 } else {
95 const IntegerValue deduction =
96 std::min(integer_literal.bound, it->second);
97 DCHECK_GT(deduction, level_zero_lower_bounds_[var]);
98 DCHECK_GT(deduction, integer_trail_->LevelZeroLowerBound(var));
99
100 // TODO(user): support Enqueueing level zero fact at a positive level.
101 // That is, do not loose the info on backtrack. This should be doable. It
102 // is also why we return a bool in case of conflict when pushing
103 // deduction.
104 ++num_deductions_;
105 level_zero_lower_bounds_[var] = deduction;
106 new_level_zero_bounds_.Set(var);
107
108 VLOG(1) << "Deduction old: "
110 var, integer_trail_->LevelZeroLowerBound(var))
111 << " new: " << IntegerLiteral::GreaterOrEqual(var, deduction);
112
113 // The entries that are equal to the min no longer need to be stored once
114 // the level zero bound is enqueued.
115 if (it->second == deduction) {
116 bounds_.erase(it);
117 }
118 if (integer_literal.bound == deduction) {
119 bounds_.erase(std::make_pair(literal.Index(), var));
120
121 // No need to update var_to_bounds_ in this case.
122 return;
123 }
124 }
125 }
126
127 // While the code above deal correctly with optionality, we cannot just
128 // register a literal => bound for an optional variable, because the equation
129 // might end up in the LP which do not handle them correctly.
130 //
131 // TODO(user): Maybe we can handle this case somehow, as long as every
132 // constraint using this bound is protected by the variable optional literal.
133 // Alternativelly we could disable optional variable when we are at
134 // linearization level 2.
135 if (integer_trail_->IsOptional(var)) return;
136
137 // If we have a new implied bound and the literal has a view, add it to
138 // var_to_bounds_. Note that we might add more than one entry with the same
139 // literal_view, and we will later need to lazily clean the vector up.
140 if (integer_encoder_->GetLiteralView(literal) != kNoIntegerVariable) {
141 if (var_to_bounds_.size() <= var) {
142 var_to_bounds_.resize(var.value() + 1);
143 has_implied_bounds_.Resize(var + 1);
144 }
145 ++num_enqueued_in_var_to_bounds_;
146 has_implied_bounds_.Set(var);
147 var_to_bounds_[var].push_back({integer_encoder_->GetLiteralView(literal),
148 integer_literal.bound, true});
149 } else if (integer_encoder_->GetLiteralView(literal.Negated()) !=
151 if (var_to_bounds_.size() <= var) {
152 var_to_bounds_.resize(var.value() + 1);
153 has_implied_bounds_.Resize(var + 1);
154 }
155 ++num_enqueued_in_var_to_bounds_;
156 has_implied_bounds_.Set(var);
157 var_to_bounds_[var].push_back(
158 {integer_encoder_->GetLiteralView(literal.Negated()),
159 integer_literal.bound, false});
160 }
161}
162
163const std::vector<ImpliedBoundEntry>& ImpliedBounds::GetImpliedBounds(
164 IntegerVariable var) {
165 if (var >= var_to_bounds_.size()) return empty_implied_bounds_;
166
167 // Lazily remove obsolete entries from the vector.
168 //
169 // TODO(user): Check no duplicate and remove old entry if the enforcement
170 // is tighter.
171 int new_size = 0;
172 std::vector<ImpliedBoundEntry>& ref = var_to_bounds_[var];
173 const IntegerValue level_zero_lb = std::max(
174 level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
175 level_zero_lower_bounds_[var] = level_zero_lb;
176 for (const ImpliedBoundEntry& entry : ref) {
177 if (entry.lower_bound <= level_zero_lb) continue;
178 ref[new_size++] = entry;
179 }
180 ref.resize(new_size);
181
182 return ref;
183}
184
186 IntegerVariable var,
187 IntegerValue value) {
188 if (!VariableIsPositive(var)) {
189 var = NegationOf(var);
190 value = -value;
191 }
192 literal_to_var_to_value_[literal.Index()][var] = value;
193}
194
196 if (!parameters_.use_implied_bounds()) return;
197
198 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 1);
199 tmp_integer_literals_.clear();
200 integer_trail_->AppendNewBounds(&tmp_integer_literals_);
201 for (const IntegerLiteral lit : tmp_integer_literals_) {
202 Add(first_decision, lit);
203 }
204}
205
207 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
208 for (const IntegerVariable var :
209 new_level_zero_bounds_.PositionsSetAtLeastOnce()) {
210 if (!integer_trail_->Enqueue(
211 IntegerLiteral::GreaterOrEqual(var, level_zero_lower_bounds_[var]),
212 {}, {})) {
213 return false;
214 }
215 }
216 new_level_zero_bounds_.SparseClearAll();
217 return sat_solver_->FinishPropagation();
218}
219
220} // namespace sat
221} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:891
#define VLOG(verboselevel)
Definition: base/logging.h:979
void resize(size_type new_size)
size_type size() const
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var, IntegerValue value)
void Add(Literal literal, IntegerLiteral integer_literal)
void ProcessIntegerTrail(Literal first_decision)
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:454
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1028
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1412
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1407
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition: integer.cc:1785
bool IsOptional(IntegerVariable i) const
Definition: integer.h:656
int64_t value
IntVar * var
Definition: expr_array.cc:1874
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
const IntegerVariable kNoIntegerVariable(-1)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:138
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:85
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1309