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