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