OR-Tools  9.2
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#include <limits>
18
19#include "ortools/sat/integer.h"
21
22namespace operations_research {
23namespace sat {
24
25// Just display some global statistics on destruction.
27 VLOG(1) << num_deductions_ << " enqueued deductions.";
28 VLOG(1) << bounds_.size() << " implied bounds stored.";
29 VLOG(1) << num_enqueued_in_var_to_bounds_
30 << " implied bounds with view stored.";
31}
32
34 if (!parameters_.use_implied_bounds()) return;
35 const IntegerVariable var = integer_literal.var;
36
37 // Update our local level-zero bound.
38 if (var >= level_zero_lower_bounds_.size()) {
39 level_zero_lower_bounds_.resize(var.value() + 1, kMinIntegerValue);
40 new_level_zero_bounds_.Resize(var + 1);
41 }
42 level_zero_lower_bounds_[var] = std::max(
43 level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
44
45 // Ignore any Add() with a bound worse than the level zero one.
46 // TODO(user): Check that this never happen? it shouldn't.
47 if (integer_literal.bound <= level_zero_lower_bounds_[var]) {
48 return;
49 }
50
51 // We skip any IntegerLiteral referring to a variable with only two
52 // consecutive possible values. This is because, once shifted this will
53 // already be a variable in [0, 1] so we shouldn't gain much by substituing
54 // it.
55 if (integer_trail_->LevelZeroLowerBound(var) + 1 >=
56 integer_trail_->LevelZeroUpperBound(var)) {
57 return;
58 }
59
60 // Add or update the current bound.
61 const auto key = std::make_pair(literal.Index(), var);
62 auto insert_result = bounds_.insert({key, integer_literal.bound});
63 if (!insert_result.second) {
64 if (insert_result.first->second < integer_literal.bound) {
65 insert_result.first->second = integer_literal.bound;
66 } else {
67 // No new info.
68 return;
69 }
70 }
71
72 // Checks if the variable is now fixed.
73 if (integer_trail_->LevelZeroUpperBound(var) == integer_literal.bound) {
75 } else {
76 const auto it =
77 bounds_.find(std::make_pair(literal.Index(), NegationOf(var)));
78 if (it != bounds_.end() && it->second == -integer_literal.bound) {
80 }
81 }
82
83 // Check if we have any deduction. Since at least one of (literal,
84 // literal.Negated()) must be true, we can take the min bound as valid at
85 // level zero.
86 //
87 // TODO(user): Like in probing, we can also create hole in the domain if there
88 // is some implied bounds for (literal.NegatedIndex, NegagtionOf(var)) that
89 // crosses integer_literal.bound.
90 const auto it = bounds_.find(std::make_pair(literal.NegatedIndex(), var));
91 if (it != bounds_.end()) {
92 if (it->second <= level_zero_lower_bounds_[var]) {
93 // The other bounds is worse than the new level-zero bound which can
94 // happen because of lazy update, so here we just remove it.
95 bounds_.erase(it);
96 } else {
97 const IntegerValue deduction =
98 std::min(integer_literal.bound, it->second);
99 DCHECK_GT(deduction, level_zero_lower_bounds_[var]);
100 DCHECK_GT(deduction, integer_trail_->LevelZeroLowerBound(var));
101
102 // TODO(user): support Enqueueing level zero fact at a positive level.
103 // That is, do not loose the info on backtrack. This should be doable. It
104 // is also why we return a bool in case of conflict when pushing
105 // deduction.
106 ++num_deductions_;
107 level_zero_lower_bounds_[var] = deduction;
108 new_level_zero_bounds_.Set(var);
109
110 VLOG(1) << "Deduction old: "
112 var, integer_trail_->LevelZeroLowerBound(var))
113 << " new: " << IntegerLiteral::GreaterOrEqual(var, deduction);
114
115 // The entries that are equal to the min no longer need to be stored once
116 // the level zero bound is enqueued.
117 if (it->second == deduction) {
118 bounds_.erase(it);
119 }
120 if (integer_literal.bound == deduction) {
121 bounds_.erase(std::make_pair(literal.Index(), var));
122
123 // No need to update var_to_bounds_ in this case.
124 return;
125 }
126 }
127 }
128
129 // While the code above deal correctly with optionality, we cannot just
130 // register a literal => bound for an optional variable, because the equation
131 // might end up in the LP which do not handle them correctly.
132 //
133 // TODO(user): Maybe we can handle this case somehow, as long as every
134 // constraint using this bound is protected by the variable optional literal.
135 // Alternativelly we could disable optional variable when we are at
136 // linearization level 2.
137 if (integer_trail_->IsOptional(var)) return;
138
139 // If we have a new implied bound and the literal has a view, add it to
140 // var_to_bounds_. Note that we might add more than one entry with the same
141 // literal_view, and we will later need to lazily clean the vector up.
142 if (integer_encoder_->GetLiteralView(literal) != kNoIntegerVariable) {
143 if (var_to_bounds_.size() <= var) {
144 var_to_bounds_.resize(var.value() + 1);
145 has_implied_bounds_.Resize(var + 1);
146 }
147 ++num_enqueued_in_var_to_bounds_;
148 has_implied_bounds_.Set(var);
149 var_to_bounds_[var].push_back({integer_encoder_->GetLiteralView(literal),
150 integer_literal.bound, true});
151 } else if (integer_encoder_->GetLiteralView(literal.Negated()) !=
153 if (var_to_bounds_.size() <= var) {
154 var_to_bounds_.resize(var.value() + 1);
155 has_implied_bounds_.Resize(var + 1);
156 }
157 ++num_enqueued_in_var_to_bounds_;
158 has_implied_bounds_.Set(var);
159 var_to_bounds_[var].push_back(
160 {integer_encoder_->GetLiteralView(literal.Negated()),
161 integer_literal.bound, false});
162 }
163}
164
165const std::vector<ImpliedBoundEntry>& ImpliedBounds::GetImpliedBounds(
166 IntegerVariable var) {
167 if (var >= var_to_bounds_.size()) return empty_implied_bounds_;
168
169 // Lazily remove obsolete entries from the vector.
170 //
171 // TODO(user): Check no duplicate and remove old entry if the enforcement
172 // is tighter.
173 int new_size = 0;
174 std::vector<ImpliedBoundEntry>& ref = var_to_bounds_[var];
175 const IntegerValue level_zero_lb = std::max(
176 level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
177 level_zero_lower_bounds_[var] = level_zero_lb;
178 for (const ImpliedBoundEntry& entry : ref) {
179 if (entry.lower_bound <= level_zero_lb) continue;
180 ref[new_size++] = entry;
181 }
182 ref.resize(new_size);
183
184 return ref;
185}
186
188 IntegerVariable var,
189 IntegerValue value) {
190 if (!VariableIsPositive(var)) {
191 var = NegationOf(var);
192 value = -value;
193 }
194 literal_to_var_to_value_[literal.Index()][var] = value;
195}
196
198 if (!parameters_.use_implied_bounds()) return;
199
200 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 1);
201 tmp_integer_literals_.clear();
202 integer_trail_->AppendNewBounds(&tmp_integer_literals_);
203 for (const IntegerLiteral lit : tmp_integer_literals_) {
204 Add(first_decision, lit);
205 }
206}
207
209 IntegerVariable var, const std::vector<ValueLiteralPair>& encoding,
210 int exactly_one_index) {
211 var_to_index_to_element_encodings_[var][exactly_one_index] = encoding;
212}
213
214const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
216 const auto& it = var_to_index_to_element_encodings_.find(var);
217 if (it == var_to_index_to_element_encodings_.end()) {
218 return empty_element_encoding_;
219 } else {
220 return it->second;
221 }
222}
223
224const std::vector<IntegerVariable>& ImpliedBounds::GetElementEncodedVariables()
225 const {
226 return element_encoded_variables_;
227}
228
230 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
231 for (const IntegerVariable var :
232 new_level_zero_bounds_.PositionsSetAtLeastOnce()) {
233 if (!integer_trail_->Enqueue(
234 IntegerLiteral::GreaterOrEqual(var, level_zero_lower_bounds_[var]),
235 {}, {})) {
236 return false;
237 }
238 }
239 new_level_zero_bounds_.SparseClearAll();
240 return sat_solver_->FinishPropagation();
241}
242
243std::string EncodingStr(const std::vector<ValueLiteralPair>& enc) {
244 std::string result;
245 for (const ValueLiteralPair& term : enc) {
246 absl::StrAppend(&result, term.literal.DebugString(), ":",
247 term.value.value(), " ");
248 }
249 return result;
250}
251
252// If a variable has a size of 2, it is most likely reduced to an affine
253// expression pointing to a variable with domain [0,1] or [-1,0].
254// If the original variable has been removed from the model, then there are no
255// implied values from any exactly_one constraint to its domain.
256// If we are lucky, one of the literal of the exactly_one constraints, and its
257// negation are used to encode the Boolean variable of the affine.
258//
259// This may fail if exactly_one(l0, l1, l2, l3); l0 and l1 imply x = 0,
260// l2 and l3 imply x = 1. In that case, one must look at the binary
261// implications to find the missing link.
262//
263// TODO(user): Consider removing this once we are more complete in our implied
264// bounds repository. Because if we can reconcile an encoding, then any of the
265// literal in the at most one should imply a value on the boolean view use in
266// the size2 affine.
268 const AffineExpression& size2_affine, const AffineExpression& affine,
269 const std::vector<ValueLiteralPair>& affine_var_encoding, Model* model,
270 LinearConstraintBuilder* builder) {
271 IntegerEncoder* integer_encoder = model->GetOrCreate<IntegerEncoder>();
272 IntegerVariable binary = size2_affine.var;
273 if (!integer_encoder->VariableIsFullyEncoded(binary)) return false;
274 const std::vector<ValueLiteralPair>& size2_enc =
275 integer_encoder->FullDomainEncoding(binary);
276 CHECK_EQ(2, size2_enc.size());
277 Literal lit0 = size2_enc[0].literal;
278 IntegerValue value0 =
279 size2_enc[0].value * size2_affine.coeff + size2_affine.constant;
280 Literal lit1 = size2_enc[1].literal;
281 IntegerValue value1 =
282 size2_enc[1].value * size2_affine.coeff + size2_affine.constant;
283 for (const auto& [unused, candidate_literal] : affine_var_encoding) {
284 if (candidate_literal == lit1) {
285 std::swap(lit0, lit1);
286 std::swap(value0, value1);
287 }
288 if (candidate_literal != lit0) continue;
289
290 // Compute the minimum energy.
291 IntegerValue min_energy = kMaxIntegerValue;
292 for (const auto& [value, literal] : affine_var_encoding) {
293 const IntegerValue energy = literal == lit0
294 ? value0 * affine.ValueAt(value)
295 : value1 * affine.ValueAt(value);
296 min_energy = std::min(energy, min_energy);
297 }
298
299 // Build the energy expression.
300 builder->Clear();
301 builder->AddConstant(min_energy);
302 for (const auto& [value, literal] : affine_var_encoding) {
303 const IntegerValue energy = literal == lit0
304 ? value0 * affine.ValueAt(value)
305 : value1 * affine.ValueAt(value);
306 if (energy > min_energy) {
307 if (!builder->AddLiteralTerm(literal, energy - min_energy)) {
308 return false;
309 }
310 }
311 }
312 return true;
313 }
314
315 return false;
316}
317
318// TODO(user): Experiment with x * x where constants = 0, x is
319// fully encoded, and the domain is small.
321 const AffineExpression& right, Model* model,
322 LinearConstraintBuilder* builder) {
323 CHECK(builder != nullptr);
324 builder->Clear();
325
326 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
327 ImpliedBounds* implied_bounds = model->GetOrCreate<ImpliedBounds>();
328
329 if (integer_trail->IsFixed(left)) {
330 const IntegerValue value = integer_trail->FixedValue(left);
331 builder->AddTerm(right, value);
332 return true;
333 }
334
335 if (integer_trail->IsFixed(right)) {
336 const IntegerValue value = integer_trail->FixedValue(right);
337 builder->AddTerm(left, value);
338 return true;
339 }
340
341 // Linearization is possible if both left and right have the same Boolean
342 // variable.
343 if (PositiveVariable(left.var) == PositiveVariable(right.var) &&
344 integer_trail->LowerBound(PositiveVariable(left.var)) == 0 &&
345 integer_trail->UpperBound(PositiveVariable(left.var)) == 1) {
346 const IntegerValue left_coeff =
347 VariableIsPositive(left.var) ? left.coeff : -left.coeff;
348 const IntegerValue right_coeff =
349 VariableIsPositive(right.var) ? right.coeff : -right.coeff;
350 builder->AddTerm(PositiveVariable(left.var),
351 left_coeff * right_coeff + left.constant * right_coeff +
352 left_coeff * right.constant);
353 builder->AddConstant(left.constant * right.constant);
354 return true;
355 }
356
357 // Fill in the encodings for the left variable.
358 const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
359 left_encodings = implied_bounds->GetElementEncodings(left.var);
360
361 // Fill in the encodings for the right variable.
362 const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
363 right_encodings = implied_bounds->GetElementEncodings(right.var);
364
365 std::vector<int> compatible_keys;
366 for (const auto& [index, encoding] : left_encodings) {
367 if (right_encodings.contains(index)) {
368 compatible_keys.push_back(index);
369 }
370 }
371
372 if (compatible_keys.empty()) {
373 if (integer_trail->InitialVariableDomain(left.var).Size() == 2) {
374 for (const auto& [index, right_encoding] : right_encodings) {
375 if (TryToReconcileEncodings(left, right, right_encoding, model,
376 builder)) {
377 return true;
378 }
379 }
380 }
381 if (integer_trail->InitialVariableDomain(right.var).Size() == 2) {
382 for (const auto& [index, left_encoding] : left_encodings) {
383 if (TryToReconcileEncodings(right, left, left_encoding, model,
384 builder)) {
385 return true;
386 }
387 }
388 }
389 return false;
390 }
391
392 if (compatible_keys.size() > 1) {
393 VLOG(1) << "More than one exactly_one involved in the encoding of the two "
394 "variables";
395 }
396
397 // Select the compatible encoding with the minimum index.
398 const int min_index =
399 *std::min_element(compatible_keys.begin(), compatible_keys.end());
400 // By construction, encodings follow the order of literals in the exactly_one
401 // constraint.
402 const std::vector<ValueLiteralPair>& left_encoding =
403 left_encodings.at(min_index);
404 const std::vector<ValueLiteralPair>& right_encoding =
405 right_encodings.at(min_index);
406 DCHECK_EQ(left_encoding.size(), right_encoding.size());
407
408 // Compute the min energy.
409 IntegerValue min_energy = kMaxIntegerValue;
410 for (int i = 0; i < left_encoding.size(); ++i) {
411 const IntegerValue energy = left.ValueAt(left_encoding[i].value) *
412 right.ValueAt(right_encoding[i].value);
413 min_energy = std::min(min_energy, energy);
414 }
415
416 // Build the linear formulation of the energy.
417 for (int i = 0; i < left_encoding.size(); ++i) {
418 const IntegerValue energy = left.ValueAt(left_encoding[i].value) *
419 right.ValueAt(right_encoding[i].value);
420 if (energy == min_energy) continue;
421 DCHECK_GT(energy, min_energy);
422 const Literal lit = left_encoding[i].literal;
423 DCHECK_EQ(lit, right_encoding[i].literal);
424
425 if (!builder->AddLiteralTerm(lit, energy - min_energy)) {
426 return false;
427 }
428 }
429 builder->AddConstant(min_energy);
430 return true;
431}
432
434 LinearExpression result;
436 return result;
437}
438
440 return !expr.vars.empty() ||
442}
443
444void LinearizeInnerProduct(const std::vector<AffineExpression>& left,
445 const std::vector<AffineExpression>& right,
446 Model* model,
447 std::vector<LinearExpression>* energies) {
448 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
449 for (int i = 0; i < left.size(); ++i) {
451 if (DetectLinearEncodingOfProducts(left[i], right[i], model, &builder)) {
452 VLOG(3) << "linearized energy: "
453 << builder.BuildExpression().DebugString();
454 energies->push_back(builder.BuildExpression());
455 } else {
456 VLOG(2) << "Product is not linearizable: demands "
457 << left[i].DebugString() << " with var domain "
458 << integer_trail->InitialVariableDomain(left[i].var)
459 << ", size = " << right[i].DebugString() << " with var domain "
460 << integer_trail->InitialVariableDomain(right[i].var);
461 energies->push_back(NotLinearizedEnergy());
462 }
463 }
464}
465
466} // namespace sat
467} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:895
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:890
#define VLOG(verboselevel)
Definition: base/logging.h:983
void resize(size_type new_size)
size_type size() const
int64_t Size() const
Returns the number of elements in the domain.
void Set(IntegerType index)
Definition: bitset.h:804
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:814
void Resize(IntegerType size)
Definition: bitset.h:790
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var, IntegerValue value)
const absl::flat_hash_map< int, std::vector< ValueLiteralPair > > & GetElementEncodings(IntegerVariable var)
void Add(Literal literal, IntegerLiteral integer_literal)
void AddElementEncoding(IntegerVariable var, const std::vector< ValueLiteralPair > &encoding, int exactly_one_index)
const std::vector< IntegerVariable > & GetElementEncodedVariables() const
void ProcessIntegerTrail(Literal first_decision)
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:493
std::vector< ValueLiteralPair > FullDomainEncoding(IntegerVariable var) const
Definition: integer.cc:114
bool VariableIsFullyEncoded(IntegerVariable var) const
Definition: integer.cc:79
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1027
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1443
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1439
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1524
IntegerValue FixedValue(IntegerVariable i) const
Definition: integer.h:1447
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1519
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition: integer.cc:1784
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1435
bool IsOptional(IntegerVariable i) const
Definition: integer.h:695
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:664
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff)
void AddTerm(IntegerVariable var, IntegerValue coeff)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:262
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
void LinearizeInnerProduct(const std::vector< AffineExpression > &left, const std::vector< AffineExpression > &right, Model *model, std::vector< LinearExpression > *energies)
std::string EncodingStr(const std::vector< ValueLiteralPair > &enc)
const IntegerVariable kNoIntegerVariable(-1)
LinearExpression NotLinearizedEnergy()
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:143
bool ProductIsLinearized(const LinearExpression &expr)
bool DetectLinearEncodingOfProducts(const AffineExpression &left, const AffineExpression &right, Model *model, LinearConstraintBuilder *builder)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:30
bool TryToReconcileEncodings(const AffineExpression &size2_affine, const AffineExpression &affine, const std::vector< ValueLiteralPair > &affine_var_encoding, Model *model, LinearConstraintBuilder *builder)
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:139
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:85
int index
Definition: pack.cc:509
int64_t energy
Definition: resource.cc:354
IntegerValue ValueAt(IntegerValue var_value) const
Definition: integer.h:267
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1377