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 
14 #include "ortools/sat/sat_decision.h"
15 
16 #include <cstdint>
17 
18 #include "ortools/sat/util.h"
19 
20 namespace operations_research {
21 namespace sat {
22 
SatDecisionPolicy(Model * model)23 SatDecisionPolicy::SatDecisionPolicy(Model* model)
24     : parameters_(*(model->GetOrCreate<SatParameters>())),
25       trail_(*model->GetOrCreate<Trail>()),
26       random_(model->GetOrCreate<ModelRandomGenerator>()) {}
27 
IncreaseNumVariables(int num_variables)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 
BeforeConflict(int trail_index)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 
RephaseIfNeeded()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 
ResetDecisionHeuristic()128 void SatDecisionPolicy::ResetDecisionHeuristic() {
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 
ResetInitialPolarity(int from,bool inverted)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 
UseLongestAssignmentAsInitialPolarity()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 
FlipCurrentPolarity()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 
RandomizeCurrentPolarity()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 
InitializeVariableOrdering()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 
SetAssignmentPreference(Literal literal,double weight)250 void SatDecisionPolicy::SetAssignmentPreference(Literal literal,
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 
AllPreferences() const265 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 
UpdateWeightedSign(const std::vector<LiteralWithCoeff> & terms,Coefficient rhs)279 void SatDecisionPolicy::UpdateWeightedSign(
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 
BumpVariableActivities(const std::vector<Literal> & literals)289 void SatDecisionPolicy::BumpVariableActivities(
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 
RescaleVariableActivities(double scaling_factor)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 
UpdateVariableActivityIncrement()334 void SatDecisionPolicy::UpdateVariableActivityIncrement() {
335   variable_activity_increment_ *= 1.0 / parameters_.variable_activity_decay();
336 }
337 
NextBranch()338 Literal SatDecisionPolicy::NextBranch() {
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 
PqInsertOrUpdate(BooleanVariable var)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 
Untrail(int target_trail_index)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
477