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