OR-Tools  9.3
sat_decision.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 <cstdint>
18#include <random>
19#include <utility>
20#include <vector>
21
24#include "ortools/sat/model.h"
27#include "ortools/sat/sat_parameters.pb.h"
28#include "ortools/sat/util.h"
29#include "ortools/util/bitset.h"
32
33namespace operations_research {
34namespace sat {
35
37 : parameters_(*(model->GetOrCreate<SatParameters>())),
38 trail_(*model->GetOrCreate<Trail>()),
39 random_(model->GetOrCreate<ModelRandomGenerator>()) {}
40
42 const int old_num_variables = activities_.size();
43 DCHECK_GE(num_variables, activities_.size());
44
45 activities_.resize(num_variables, parameters_.initial_variables_activity());
46 tie_breakers_.resize(num_variables, 0.0);
47 num_bumps_.resize(num_variables, 0);
48 pq_need_update_for_var_at_trail_index_.IncreaseSize(num_variables);
49
50 weighted_sign_.resize(num_variables, 0.0);
51
52 has_forced_polarity_.resize(num_variables, false);
53 forced_polarity_.resize(num_variables);
54 has_target_polarity_.resize(num_variables, false);
55 target_polarity_.resize(num_variables);
56 var_polarity_.resize(num_variables);
57
58 ResetInitialPolarity(/*from=*/old_num_variables);
59
60 // Update the priority queue. Note that each addition is in O(1) because
61 // the priority is 0.0.
62 var_ordering_.Reserve(num_variables);
63 if (var_ordering_is_initialized_) {
64 for (BooleanVariable var(old_num_variables); var < num_variables; ++var) {
65 var_ordering_.Add({var, 0.0, activities_[var]});
66 }
67 }
68}
69
70void SatDecisionPolicy::BeforeConflict(int trail_index) {
71 if (parameters_.use_erwa_heuristic()) {
72 ++num_conflicts_;
73 num_conflicts_stack_.push_back({trail_.Index(), 1});
74 }
75
76 if (trail_index > target_length_) {
77 target_length_ = trail_index;
78 has_target_polarity_.assign(has_target_polarity_.size(), false);
79 for (int i = 0; i < trail_index; ++i) {
80 const Literal l = trail_[i];
81 has_target_polarity_[l.Variable()] = true;
82 target_polarity_[l.Variable()] = l.IsPositive();
83 }
84 }
85
86 if (trail_index > best_partial_assignment_.size()) {
87 best_partial_assignment_.assign(&trail_[0], &trail_[trail_index]);
88 }
89
90 --num_conflicts_until_rephase_;
91 RephaseIfNeeded();
92}
93
94void SatDecisionPolicy::RephaseIfNeeded() {
95 if (parameters_.polarity_rephase_increment() <= 0) return;
96 if (num_conflicts_until_rephase_ > 0) return;
97
98 VLOG(1) << "End of polarity phase " << polarity_phase_
99 << " target_length: " << target_length_
100 << " best_length: " << best_partial_assignment_.size();
101
102 ++polarity_phase_;
103 num_conflicts_until_rephase_ =
104 parameters_.polarity_rephase_increment() * (polarity_phase_ + 1);
105
106 // We always reset the target each time we change phase.
107 target_length_ = 0;
108 has_target_polarity_.assign(has_target_polarity_.size(), false);
109
110 // Cycle between different initial polarities. Note that we already start by
111 // the default polarity, and this code is reached the first time with a
112 // polarity_phase_ of 1.
113 switch (polarity_phase_ % 8) {
114 case 0:
115 ResetInitialPolarity(/*from=*/0);
116 break;
117 case 1:
118 UseLongestAssignmentAsInitialPolarity();
119 break;
120 case 2:
121 ResetInitialPolarity(/*from=*/0, /*inverted=*/true);
122 break;
123 case 3:
124 UseLongestAssignmentAsInitialPolarity();
125 break;
126 case 4:
127 RandomizeCurrentPolarity();
128 break;
129 case 5:
130 UseLongestAssignmentAsInitialPolarity();
131 break;
132 case 6:
133 FlipCurrentPolarity();
134 break;
135 case 7:
136 UseLongestAssignmentAsInitialPolarity();
137 break;
138 }
139}
140
142 const int num_variables = activities_.size();
143 variable_activity_increment_ = 1.0;
144 activities_.assign(num_variables, parameters_.initial_variables_activity());
145 tie_breakers_.assign(num_variables, 0.0);
146 num_bumps_.assign(num_variables, 0);
147 var_ordering_.Clear();
148
149 polarity_phase_ = 0;
150 num_conflicts_until_rephase_ = parameters_.polarity_rephase_increment();
151
152 ResetInitialPolarity(/*from=*/0);
153 has_target_polarity_.assign(num_variables, false);
154 has_forced_polarity_.assign(num_variables, false);
155 best_partial_assignment_.clear();
156
157 num_conflicts_ = 0;
158 num_conflicts_stack_.clear();
159
160 var_ordering_is_initialized_ = false;
161}
162
163void SatDecisionPolicy::ResetInitialPolarity(int from, bool inverted) {
164 // Sets the initial polarity.
165 //
166 // TODO(user): The WEIGHTED_SIGN one are currently slightly broken because the
167 // weighted_sign_ is updated after this has been called. It requires a call
168 // to ResetDecisionHeuristic() after all the constraint have been added. Fix.
169 // On another hand, this is only used with SolveWithRandomParameters() that
170 // does call this function.
171 const int num_variables = activities_.size();
172 for (BooleanVariable var(from); var < num_variables; ++var) {
173 switch (parameters_.initial_polarity()) {
174 case SatParameters::POLARITY_TRUE:
175 var_polarity_[var] = inverted ? false : true;
176 break;
177 case SatParameters::POLARITY_FALSE:
178 var_polarity_[var] = inverted ? true : false;
179 break;
180 case SatParameters::POLARITY_RANDOM:
181 var_polarity_[var] = std::uniform_int_distribution<int>(0, 1)(*random_);
182 break;
183 case SatParameters::POLARITY_WEIGHTED_SIGN:
184 var_polarity_[var] = weighted_sign_[var] > 0;
185 break;
186 case SatParameters::POLARITY_REVERSE_WEIGHTED_SIGN:
187 var_polarity_[var] = weighted_sign_[var] < 0;
188 break;
189 }
190 }
191}
192
193void SatDecisionPolicy::UseLongestAssignmentAsInitialPolarity() {
194 // In this special case, we just overwrite partially the current fixed
195 // polarity and reset the best best_partial_assignment_ for the next such
196 // phase.
197 for (const Literal l : best_partial_assignment_) {
198 var_polarity_[l.Variable()] = l.IsPositive();
199 }
200 best_partial_assignment_.clear();
201}
202
203void SatDecisionPolicy::FlipCurrentPolarity() {
204 const int num_variables = var_polarity_.size();
205 for (BooleanVariable var; var < num_variables; ++var) {
206 var_polarity_[var] = !var_polarity_[var];
207 }
208}
209
210void SatDecisionPolicy::RandomizeCurrentPolarity() {
211 const int num_variables = var_polarity_.size();
212 for (BooleanVariable var; var < num_variables; ++var) {
213 var_polarity_[var] = std::uniform_int_distribution<int>(0, 1)(*random_);
214 }
215}
216
217void SatDecisionPolicy::InitializeVariableOrdering() {
218 const int num_variables = activities_.size();
219
220 // First, extract the variables without activity, and add the other to the
221 // priority queue.
222 var_ordering_.Clear();
223 tmp_variables_.clear();
224 for (BooleanVariable var(0); var < num_variables; ++var) {
225 if (!trail_.Assignment().VariableIsAssigned(var)) {
226 if (activities_[var] > 0.0) {
227 var_ordering_.Add(
228 {var, static_cast<float>(tie_breakers_[var]), activities_[var]});
229 } else {
230 tmp_variables_.push_back(var);
231 }
232 }
233 }
234
235 // Set the order of the other according to the parameters_.
236 // Note that this is just a "preference" since the priority queue will kind
237 // of randomize this. However, it is more efficient than using the tie_breaker
238 // which add a big overhead on the priority queue.
239 //
240 // TODO(user): Experiment and come up with a good set of heuristics.
241 switch (parameters_.preferred_variable_order()) {
242 case SatParameters::IN_ORDER:
243 break;
244 case SatParameters::IN_REVERSE_ORDER:
245 std::reverse(tmp_variables_.begin(), tmp_variables_.end());
246 break;
247 case SatParameters::IN_RANDOM_ORDER:
248 std::shuffle(tmp_variables_.begin(), tmp_variables_.end(), *random_);
249 break;
250 }
251
252 // Add the variables without activity to the queue (in the default order)
253 for (const BooleanVariable var : tmp_variables_) {
254 var_ordering_.Add({var, static_cast<float>(tie_breakers_[var]), 0.0});
255 }
256
257 // Finish the queue initialization.
258 pq_need_update_for_var_at_trail_index_.ClearAndResize(num_variables);
259 pq_need_update_for_var_at_trail_index_.SetAllBefore(trail_.Index());
260 var_ordering_is_initialized_ = true;
261}
262
264 double weight) {
265 if (!parameters_.use_optimization_hints()) return;
266 DCHECK_GE(weight, 0.0);
267 DCHECK_LE(weight, 1.0);
268
269 has_forced_polarity_[literal.Variable()] = true;
270 forced_polarity_[literal.Variable()] = literal.IsPositive();
271
272 // The tie_breaker is changed, so we need to reinitialize the priority queue.
273 // Note that this doesn't change the activity though.
274 tie_breakers_[literal.Variable()] = weight;
275 var_ordering_is_initialized_ = false;
276}
277
278std::vector<std::pair<Literal, double>> SatDecisionPolicy::AllPreferences()
279 const {
280 std::vector<std::pair<Literal, double>> prefs;
281 for (BooleanVariable var(0); var < var_polarity_.size(); ++var) {
282 // TODO(user): we currently assume that if the tie_breaker is zero then
283 // no preference was set (which is not 100% correct). Fix that.
284 const double value = var_ordering_.GetElement(var.value()).tie_breaker;
285 if (value > 0.0) {
286 prefs.push_back(std::make_pair(Literal(var, var_polarity_[var]), value));
287 }
288 }
289 return prefs;
290}
291
293 const std::vector<LiteralWithCoeff>& terms, Coefficient rhs) {
294 for (const LiteralWithCoeff& term : terms) {
295 const double weight = static_cast<double>(term.coefficient.value()) /
296 static_cast<double>(rhs.value());
297 weighted_sign_[term.literal.Variable()] +=
298 term.literal.IsPositive() ? -weight : weight;
299 }
300}
301
303 const std::vector<Literal>& literals) {
304 if (parameters_.use_erwa_heuristic()) {
305 for (const Literal literal : literals) {
306 // Note that we don't really need to bump level 0 variables since they
307 // will never be backtracked over. However it is faster to simply bump
308 // them.
309 ++num_bumps_[literal.Variable()];
310 }
311 return;
312 }
313
314 const double max_activity_value = parameters_.max_variable_activity_value();
315 for (const Literal literal : literals) {
316 const BooleanVariable var = literal.Variable();
317 const int level = trail_.Info(var).level;
318 if (level == 0) continue;
319 activities_[var] += variable_activity_increment_;
320 pq_need_update_for_var_at_trail_index_.Set(trail_.Info(var).trail_index);
321 if (activities_[var] > max_activity_value) {
322 RescaleVariableActivities(1.0 / max_activity_value);
323 }
324 }
325}
326
327void SatDecisionPolicy::RescaleVariableActivities(double scaling_factor) {
328 variable_activity_increment_ *= scaling_factor;
329 for (BooleanVariable var(0); var < activities_.size(); ++var) {
330 activities_[var] *= scaling_factor;
331 }
332
333 // When rescaling the activities of all the variables, the order of the
334 // active variables in the heap will not change, but we still need to update
335 // their weights so that newly inserted elements will compare correctly with
336 // already inserted ones.
337 //
338 // IMPORTANT: we need to reset the full heap from scratch because just
339 // multiplying the current weight by scaling_factor is not guaranteed to
340 // preserve the order. This is because the activity of two entries may go to
341 // zero and the tie-breaking ordering may change their relative order.
342 //
343 // InitializeVariableOrdering() will be called lazily only if needed.
344 var_ordering_is_initialized_ = false;
345}
346
348 variable_activity_increment_ *= 1.0 / parameters_.variable_activity_decay();
349}
350
352 // Lazily initialize var_ordering_ if needed.
353 if (!var_ordering_is_initialized_) {
354 InitializeVariableOrdering();
355 }
356
357 // Choose the variable.
358 BooleanVariable var;
359 const double ratio = parameters_.random_branches_ratio();
360 auto zero_to_one = [this]() {
361 return std::uniform_real_distribution<double>()(*random_);
362 };
363 if (ratio != 0.0 && zero_to_one() < ratio) {
364 while (true) {
365 // TODO(user): This may not be super efficient if almost all the
366 // variables are assigned.
367 std::uniform_int_distribution<int> index_dist(0,
368 var_ordering_.Size() - 1);
369 var = var_ordering_.QueueElement(index_dist(*random_)).var;
370 if (!trail_.Assignment().VariableIsAssigned(var)) break;
371 pq_need_update_for_var_at_trail_index_.Set(trail_.Info(var).trail_index);
372 var_ordering_.Remove(var.value());
373 }
374 } else {
375 // The loop is done this way in order to leave the final choice in the heap.
376 DCHECK(!var_ordering_.IsEmpty());
377 var = var_ordering_.Top().var;
378 while (trail_.Assignment().VariableIsAssigned(var)) {
379 var_ordering_.Pop();
380 pq_need_update_for_var_at_trail_index_.Set(trail_.Info(var).trail_index);
381 DCHECK(!var_ordering_.IsEmpty());
382 var = var_ordering_.Top().var;
383 }
384 }
385
386 // Choose its polarity (i.e. True of False).
387 const double random_ratio = parameters_.random_polarity_ratio();
388 if (random_ratio != 0.0 && zero_to_one() < random_ratio) {
389 return Literal(var, std::uniform_int_distribution<int>(0, 1)(*random_));
390 }
391
392 if (has_forced_polarity_[var]) return Literal(var, forced_polarity_[var]);
393 if (in_stable_phase_ && has_target_polarity_[var]) {
394 return Literal(var, target_polarity_[var]);
395 }
396 return Literal(var, var_polarity_[var]);
397}
398
399void SatDecisionPolicy::PqInsertOrUpdate(BooleanVariable var) {
400 const WeightedVarQueueElement element{
401 var, static_cast<float>(tie_breakers_[var]), activities_[var]};
402 if (var_ordering_.Contains(var.value())) {
403 // Note that the new weight should always be higher than the old one.
404 var_ordering_.IncreasePriority(element);
405 } else {
406 var_ordering_.Add(element);
407 }
408}
409
410void SatDecisionPolicy::Untrail(int target_trail_index) {
411 // TODO(user): avoid looping twice over the trail?
412 if (maybe_enable_phase_saving_ && parameters_.use_phase_saving()) {
413 for (int i = target_trail_index; i < trail_.Index(); ++i) {
414 const Literal l = trail_[i];
415 var_polarity_[l.Variable()] = l.IsPositive();
416 }
417 }
418
419 DCHECK_LT(target_trail_index, trail_.Index());
420 if (parameters_.use_erwa_heuristic()) {
421 // The ERWA parameter between the new estimation of the learning rate and
422 // the old one. TODO(user): Expose parameters for these values.
423 const double alpha = std::max(0.06, 0.4 - 1e-6 * num_conflicts_);
424
425 // This counts the number of conflicts since the assignment of the variable
426 // at the current trail_index that we are about to untrail.
427 int num_conflicts = 0;
428 int next_num_conflicts_update =
429 num_conflicts_stack_.empty() ? -1
430 : num_conflicts_stack_.back().trail_index;
431
432 int trail_index = trail_.Index();
433 while (trail_index > target_trail_index) {
434 if (next_num_conflicts_update == trail_index) {
435 num_conflicts += num_conflicts_stack_.back().count;
436 num_conflicts_stack_.pop_back();
437 next_num_conflicts_update =
438 num_conflicts_stack_.empty()
439 ? -1
440 : num_conflicts_stack_.back().trail_index;
441 }
442 const BooleanVariable var = trail_[--trail_index].Variable();
443
444 // TODO(user): This heuristic can make this code quite slow because
445 // all the untrailed variable will cause a priority queue update.
446 const int64_t num_bumps = num_bumps_[var];
447 double new_rate = 0.0;
448 if (num_bumps > 0) {
449 DCHECK_GT(num_conflicts, 0);
450 num_bumps_[var] = 0;
451 new_rate = static_cast<double>(num_bumps) / num_conflicts;
452 }
453 activities_[var] = alpha * new_rate + (1 - alpha) * activities_[var];
454 if (var_ordering_is_initialized_) PqInsertOrUpdate(var);
455 }
456 if (num_conflicts > 0) {
457 if (!num_conflicts_stack_.empty() &&
458 num_conflicts_stack_.back().trail_index == trail_.Index()) {
459 num_conflicts_stack_.back().count += num_conflicts;
460 } else {
461 num_conflicts_stack_.push_back({trail_.Index(), num_conflicts});
462 }
463 }
464 } else {
465 if (!var_ordering_is_initialized_) return;
466
467 // Trail index of the next variable that will need a priority queue update.
468 int to_update = pq_need_update_for_var_at_trail_index_.Top();
469 while (to_update >= target_trail_index) {
470 DCHECK_LT(to_update, trail_.Index());
471 PqInsertOrUpdate(trail_[to_update].Variable());
472 pq_need_update_for_var_at_trail_index_.ClearTop();
473 to_update = pq_need_update_for_var_at_trail_index_.Top();
474 }
475 }
476
477 // Invariant.
478 if (DEBUG_MODE && var_ordering_is_initialized_) {
479 for (int trail_index = trail_.Index() - 1; trail_index > target_trail_index;
480 --trail_index) {
481 const BooleanVariable var = trail_[trail_index].Variable();
482 CHECK(var_ordering_.Contains(var.value()));
483 CHECK_EQ(activities_[var], var_ordering_.GetElement(var.value()).weight);
484 }
485 }
486}
487
488} // namespace sat
489} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:893
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:895
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:896
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:894
#define DCHECK(condition)
Definition: base/logging.h:890
#define VLOG(verboselevel)
Definition: base/logging.h:984
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
size_type size() const
void IncreaseSize(int size)
Definition: bitset.h:690
void ClearAndResize(int size)
Definition: bitset.h:696
void IncreasePriority(Element element)
Definition: integer_pq.h:116
Element GetElement(int index) const
Definition: integer_pq.h:124
BooleanVariable Variable() const
Definition: sat_base.h:83
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
std::vector< std::pair< Literal, double > > AllPreferences() const
void IncreaseNumVariables(int num_variables)
Definition: sat_decision.cc:41
void SetAssignmentPreference(Literal literal, double weight)
void Untrail(int target_trail_index)
void BumpVariableActivities(const std::vector< Literal > &literals)
void UpdateWeightedSign(const std::vector< LiteralWithCoeff > &terms, Coefficient rhs)
const VariablesAssignment & Assignment() const
Definition: sat_base.h:383
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:384
bool VariableIsAssigned(BooleanVariable var) const
Definition: sat_base.h:161
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
const bool DEBUG_MODE
Definition: macros.h:24
std::tuple< int64_t, int64_t, const double > Coefficient
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:89
int64_t weight
Definition: pack.cc:510
Fractional ratio