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