Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sat_decision.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 "absl/log/check.h"
25#include "ortools/sat/model.h"
28#include "ortools/sat/sat_parameters.pb.h"
29#include "ortools/sat/util.h"
30#include "ortools/util/bitset.h"
33
34namespace operations_research {
35namespace sat {
36
38 : parameters_(*(model->GetOrCreate<SatParameters>())),
39 trail_(*model->GetOrCreate<Trail>()),
40 random_(model->GetOrCreate<ModelRandomGenerator>()) {}
41
43 const int old_num_variables = activities_.size();
44 DCHECK_GE(num_variables, activities_.size());
45
46 activities_.resize(num_variables, parameters_.initial_variables_activity());
47 tie_breakers_.resize(num_variables, 0.0);
48 num_bumps_.clear();
49 pq_need_update_for_var_at_trail_index_.IncreaseSize(num_variables);
50
51 has_forced_polarity_.resize(num_variables, false);
52 forced_polarity_.resize(num_variables);
53 has_target_polarity_.resize(num_variables, false);
54 target_polarity_.resize(num_variables);
55 var_polarity_.resize(num_variables);
56
57 ResetInitialPolarity(/*from=*/old_num_variables);
58
59 // Update the priority queue. Note that each addition is in O(1) because
60 // the priority is 0.0.
61 var_ordering_.Reserve(num_variables);
62 if (var_ordering_is_initialized_) {
63 for (BooleanVariable var(old_num_variables); var < num_variables; ++var) {
64 var_ordering_.Add({var, 0.0, activities_[var]});
65 }
66 }
67}
68
69void SatDecisionPolicy::BeforeConflict(int trail_index) {
70 if (parameters_.use_erwa_heuristic()) {
71 ++num_conflicts_;
72 num_conflicts_stack_.push_back({trail_.Index(), 1});
73 }
74
75 if (trail_index > target_length_) {
76 target_length_ = trail_index;
77 has_target_polarity_.assign(has_target_polarity_.size(), false);
78 for (int i = 0; i < trail_index; ++i) {
79 const Literal l = trail_[i];
80 has_target_polarity_[l.Variable()] = true;
81 target_polarity_[l.Variable()] = l.IsPositive();
82 }
83 }
84
85 if (trail_index > best_partial_assignment_.size()) {
86 best_partial_assignment_.assign(trail_.IteratorAt(0),
87 trail_.IteratorAt(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_.clear();
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 const int num_variables = activities_.size();
166 for (BooleanVariable var(from); var < num_variables; ++var) {
167 switch (parameters_.initial_polarity()) {
168 case SatParameters::POLARITY_TRUE:
169 var_polarity_[var] = inverted ? false : true;
170 break;
171 case SatParameters::POLARITY_FALSE:
172 var_polarity_[var] = inverted ? true : false;
173 break;
174 case SatParameters::POLARITY_RANDOM:
175 var_polarity_[var] = std::uniform_int_distribution<int>(0, 1)(*random_);
176 break;
177 }
178 }
179}
180
181void SatDecisionPolicy::UseLongestAssignmentAsInitialPolarity() {
182 // In this special case, we just overwrite partially the current fixed
183 // polarity and reset the best best_partial_assignment_ for the next such
184 // phase.
185 for (const Literal l : best_partial_assignment_) {
186 var_polarity_[l.Variable()] = l.IsPositive();
187 }
188 best_partial_assignment_.clear();
189}
190
191void SatDecisionPolicy::FlipCurrentPolarity() {
192 const int num_variables = var_polarity_.size();
193 for (BooleanVariable var; var < num_variables; ++var) {
194 var_polarity_[var] = !var_polarity_[var];
195 }
196}
197
198void SatDecisionPolicy::RandomizeCurrentPolarity() {
199 const int num_variables = var_polarity_.size();
200 for (BooleanVariable var; var < num_variables; ++var) {
201 var_polarity_[var] = std::uniform_int_distribution<int>(0, 1)(*random_);
202 }
203}
204
205void SatDecisionPolicy::InitializeVariableOrdering() {
206 const int num_variables = activities_.size();
207
208 // First, extract the variables without activity, and add the other to the
209 // priority queue.
210 var_ordering_.Clear();
211 tmp_variables_.clear();
212 for (BooleanVariable var(0); var < num_variables; ++var) {
213 if (!trail_.Assignment().VariableIsAssigned(var)) {
214 if (activities_[var] > 0.0) {
215 var_ordering_.Add({var, 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, 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 if (!parameters_.use_optimization_hints()) return;
252 DCHECK_GE(weight, 0.0);
253 DCHECK_LE(weight, 1.0);
254
255 has_forced_polarity_[literal.Variable()] = true;
256 forced_polarity_[literal.Variable()] = literal.IsPositive();
257
258 // The tie_breaker is changed, so we need to reinitialize the priority queue.
259 // Note that this doesn't change the activity though.
260 tie_breakers_[literal.Variable()] = weight;
261 var_ordering_is_initialized_ = false;
262}
263
264std::vector<std::pair<Literal, float>> SatDecisionPolicy::AllPreferences()
265 const {
266 std::vector<std::pair<Literal, float>> prefs;
267 for (BooleanVariable var(0); var < var_polarity_.size(); ++var) {
268 // TODO(user): we currently assume that if the tie_breaker is zero then
269 // no preference was set (which is not 100% correct). Fix that.
270 const float value = var_ordering_.GetElement(var.value()).tie_breaker;
271 if (value > 0.0) {
272 prefs.push_back(std::make_pair(Literal(var, var_polarity_[var]), value));
273 }
274 }
275 return prefs;
276}
277
279 absl::Span<const Literal> literals) {
280 if (parameters_.use_erwa_heuristic()) {
281 if (num_bumps_.size() != activities_.size()) {
282 num_bumps_.resize(activities_.size(), 0);
283 }
284 for (const Literal literal : literals) {
285 // Note that we don't really need to bump level 0 variables since they
286 // will never be backtracked over. However it is faster to simply bump
287 // them.
288 ++num_bumps_[literal.Variable()];
289 }
290 return;
291 }
292
293 const double max_activity_value = parameters_.max_variable_activity_value();
294 for (const Literal literal : literals) {
295 const BooleanVariable var = literal.Variable();
296 const int level = trail_.Info(var).level;
297 if (level == 0) continue;
298 activities_[var] += variable_activity_increment_;
299 pq_need_update_for_var_at_trail_index_.Set(trail_.Info(var).trail_index);
300 if (activities_[var] > max_activity_value) {
301 RescaleVariableActivities(1.0 / max_activity_value);
302 }
303 }
304}
305
306void SatDecisionPolicy::RescaleVariableActivities(double scaling_factor) {
307 variable_activity_increment_ *= scaling_factor;
308 for (BooleanVariable var(0); var < activities_.size(); ++var) {
309 activities_[var] *= scaling_factor;
310 }
311
312 // When rescaling the activities of all the variables, the order of the
313 // active variables in the heap will not change, but we still need to update
314 // their weights so that newly inserted elements will compare correctly with
315 // already inserted ones.
316 //
317 // IMPORTANT: we need to reset the full heap from scratch because just
318 // multiplying the current weight by scaling_factor is not guaranteed to
319 // preserve the order. This is because the activity of two entries may go to
320 // zero and the tie-breaking ordering may change their relative order.
321 //
322 // InitializeVariableOrdering() will be called lazily only if needed.
323 var_ordering_is_initialized_ = false;
324}
325
327 variable_activity_increment_ *= 1.0 / parameters_.variable_activity_decay();
328}
329
331 // Lazily initialize var_ordering_ if needed.
332 if (!var_ordering_is_initialized_) {
333 InitializeVariableOrdering();
334 }
335
336 // Choose the variable.
337 BooleanVariable var;
338 const double ratio = parameters_.random_branches_ratio();
339 auto zero_to_one = [this]() {
340 return std::uniform_real_distribution<double>()(*random_);
341 };
342 if (ratio != 0.0 && zero_to_one() < ratio) {
343 while (true) {
344 // TODO(user): This may not be super efficient if almost all the
345 // variables are assigned.
346 std::uniform_int_distribution<int> index_dist(0,
347 var_ordering_.Size() - 1);
348 var = var_ordering_.QueueElement(index_dist(*random_)).var;
349 if (!trail_.Assignment().VariableIsAssigned(var)) break;
350 pq_need_update_for_var_at_trail_index_.Set(trail_.Info(var).trail_index);
351 var_ordering_.Remove(var.value());
352 }
353 } else {
354 // The loop is done this way in order to leave the final choice in the heap.
355 DCHECK(!var_ordering_.IsEmpty());
356 var = var_ordering_.Top().var;
357 while (trail_.Assignment().VariableIsAssigned(var)) {
358 var_ordering_.Pop();
359 pq_need_update_for_var_at_trail_index_.Set(trail_.Info(var).trail_index);
360 DCHECK(!var_ordering_.IsEmpty());
361 var = var_ordering_.Top().var;
362 }
363 }
364
365 // Choose its polarity (i.e. True of False).
366 const double random_ratio = parameters_.random_polarity_ratio();
367 if (random_ratio != 0.0 && zero_to_one() < random_ratio) {
368 return Literal(var, std::uniform_int_distribution<int>(0, 1)(*random_));
369 }
370
371 if (has_forced_polarity_[var]) return Literal(var, forced_polarity_[var]);
372 if (in_stable_phase_ && has_target_polarity_[var]) {
373 return Literal(var, target_polarity_[var]);
374 }
375 return Literal(var, var_polarity_[var]);
376}
377
378void SatDecisionPolicy::PqInsertOrUpdate(BooleanVariable var) {
379 const WeightedVarQueueElement element{var, tie_breakers_[var],
380 activities_[var]};
381 if (var_ordering_.Contains(var.value())) {
382 // Note that the new weight should always be higher than the old one.
383 var_ordering_.IncreasePriority(element);
384 } else {
385 var_ordering_.Add(element);
386 }
387}
388
389void SatDecisionPolicy::Untrail(int target_trail_index) {
390 // TODO(user): avoid looping twice over the trail?
391 if (maybe_enable_phase_saving_ && parameters_.use_phase_saving()) {
392 for (int i = target_trail_index; i < trail_.Index(); ++i) {
393 const Literal l = trail_[i];
394 var_polarity_[l.Variable()] = l.IsPositive();
395 }
396 }
397
398 DCHECK_LT(target_trail_index, trail_.Index());
399 if (parameters_.use_erwa_heuristic()) {
400 if (num_bumps_.size() != activities_.size()) {
401 num_bumps_.resize(activities_.size(), 0);
402 }
403
404 // The ERWA parameter between the new estimation of the learning rate and
405 // the old one. TODO(user): Expose parameters for these values.
406 const double alpha = std::max(0.06, 0.4 - 1e-6 * num_conflicts_);
407
408 // This counts the number of conflicts since the assignment of the variable
409 // at the current trail_index that we are about to untrail.
410 int num_conflicts = 0;
411 int next_num_conflicts_update =
412 num_conflicts_stack_.empty() ? -1
413 : num_conflicts_stack_.back().trail_index;
414
415 int trail_index = trail_.Index();
416 while (trail_index > target_trail_index) {
417 if (next_num_conflicts_update == trail_index) {
418 num_conflicts += num_conflicts_stack_.back().count;
419 num_conflicts_stack_.pop_back();
420 next_num_conflicts_update =
421 num_conflicts_stack_.empty()
422 ? -1
423 : num_conflicts_stack_.back().trail_index;
424 }
425 const BooleanVariable var = trail_[--trail_index].Variable();
426
427 // TODO(user): This heuristic can make this code quite slow because
428 // all the untrailed variable will cause a priority queue update.
429 if (num_conflicts > 0) {
430 const int64_t num_bumps = num_bumps_[var];
431 double new_rate = 0.0;
432 if (num_bumps > 0) {
433 num_bumps_[var] = 0;
434 new_rate = static_cast<double>(num_bumps) / num_conflicts;
435 }
436 activities_[var] = alpha * new_rate + (1 - alpha) * activities_[var];
437 }
438 if (var_ordering_is_initialized_) PqInsertOrUpdate(var);
439 }
440 if (num_conflicts > 0) {
441 if (!num_conflicts_stack_.empty() &&
442 num_conflicts_stack_.back().trail_index == trail_.Index()) {
443 num_conflicts_stack_.back().count += num_conflicts;
444 } else {
445 num_conflicts_stack_.push_back({trail_.Index(), num_conflicts});
446 }
447 }
448 } else {
449 if (!var_ordering_is_initialized_) return;
450
451 // Trail index of the next variable that will need a priority queue update.
452 int to_update = pq_need_update_for_var_at_trail_index_.Top();
453 while (to_update >= target_trail_index) {
454 DCHECK_LT(to_update, trail_.Index());
455 PqInsertOrUpdate(trail_[to_update].Variable());
456 pq_need_update_for_var_at_trail_index_.ClearTop();
457 to_update = pq_need_update_for_var_at_trail_index_.Top();
458 }
459 }
460
461 // Invariant.
462 if (DEBUG_MODE && var_ordering_is_initialized_) {
463 for (int trail_index = trail_.Index() - 1; trail_index > target_trail_index;
464 --trail_index) {
465 const BooleanVariable var = trail_[trail_index].Variable();
466 CHECK(var_ordering_.Contains(var.value()));
467 CHECK_EQ(activities_[var], var_ordering_.GetElement(var.value()).weight);
468 }
469 }
470}
471
472} // namespace sat
473} // namespace operations_research
int Top() const
Returns the position of the highest bit set in O(1) or -1 if no bit is set.
Definition bitset.h:774
void ClearAndResize(int size)
Definition bitset.h:748
void ClearTop()
Clears the Top() bit and recomputes the position of the next Top().
Definition bitset.h:777
void IncreaseSize(int size)
Definition bitset.h:742
void SetAllBefore(int i)
Sets all the bits from 0 up to i-1 to 1.
Definition bitset.h:762
void IncreasePriority(Element element)
Optimized version of ChangePriority() when we know the direction.
Definition integer_pq.h:116
int Size() const
Returns the number of elements currently present.
Definition integer_pq.h:56
Element GetElement(int index) const
Returns the element with given index.
Definition integer_pq.h:124
bool Contains(int index) const
Returns true if the element with given index is currently in the queue.
Definition integer_pq.h:67
BooleanVariable Variable() const
Definition sat_base.h:87
void BumpVariableActivities(absl::Span< const Literal > literals)
std::vector< std::pair< Literal, float > > AllPreferences() const
Returns the vector of the current assignment preferences.
void SetAssignmentPreference(Literal literal, float weight)
void Untrail(int target_trail_index)
Called on Untrail() so that we can update the set of possible decisions.
std::vector< Literal >::const_iterator IteratorAt(int index) const
Definition sat_base.h:458
const AssignmentInfo & Info(BooleanVariable var) const
Definition sat_base.h:463
const VariablesAssignment & Assignment() const
Definition sat_base.h:462
bool VariableIsAssigned(BooleanVariable var) const
Returns true iff the given variable is assigned.
Definition sat_base.h:196
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
int64_t value
IntVar * var
GRBmodel * model
int literal
const bool DEBUG_MODE
Definition macros.h:24
In SWIG mode, we don't want anything besides these top-level includes.
int64_t weight
Definition pack.cc:510
Fractional ratio
int32_t trail_index
The index of this assignment in the trail.
Definition sat_base.h:263