Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sat_decision.h
Go to the documentation of this file.
1// Copyright 2010-2025 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#ifndef ORTOOLS_SAT_SAT_DECISION_H_
15#define ORTOOLS_SAT_SAT_DECISION_H_
16
17#include <cstdint>
18#include <utility>
19#include <vector>
20
21#include "absl/types/span.h"
23#include "ortools/sat/model.h"
27#include "ortools/sat/util.h"
28#include "ortools/util/bitset.h"
31
32namespace operations_research {
33namespace sat {
34
35// Implement the SAT branching policy responsible for deciding the next Boolean
36// variable to branch on, and its polarity (true or false).
38 public:
39 explicit SatDecisionPolicy(Model* model);
40
41 // Notifies that more variables are now present. Note that currently this may
42 // change the current variable order because the priority queue need to be
43 // reconstructed.
44 void IncreaseNumVariables(int num_variables);
45
46 // Reinitializes the decision heuristics (which variables to choose with which
47 // polarity) according to the current parameters. Note that this also resets
48 // the activity of the variables to 0. Note that this function is lazy, and
49 // the work will only happen on the first NextBranch() to cover the cases when
50 // this policy is not used at all.
52
53 // Returns next decision to branch upon. This shouldn't be called if all the
54 // variables are assigned.
56
57 // Bumps the activity of all variables appearing in the conflict. All literals
58 // must be currently assigned. See VSIDS decision heuristic: Chaff:
59 // Engineering an Efficient SAT Solver. M.W. Moskewicz et al. ANNUAL ACM IEEE
60 // DESIGN AUTOMATION CONFERENCE 2001.
61 void BumpVariableActivities(absl::Span<const Literal> literals);
62
63 // Updates the increment used for activity bumps. This is basically the same
64 // as decaying all the variable activities, but it is a lot more efficient.
66
67 // Called on Untrail() so that we can update the set of possible decisions.
68 void Untrail(int target_trail_index);
69
70 // Called on a new conflict before Untrail(). The trail before the given index
71 // is used in the phase saving heuristic as a partial assignment.
72 void BeforeConflict(int trail_index);
73
74 // By default, we alternate between a stable phase (better suited for finding
75 // SAT solution) and a more restart heavy phase more suited for proving UNSAT.
76 // This changes a bit the polarity heuristics and is controlled from within
77 // SatRestartPolicy.
78 void SetStablePhase(bool is_stable) { in_stable_phase_ = is_stable; }
79 bool InStablePhase() const { return in_stable_phase_; }
80
81 // This is used to temporarily disable phase_saving when we do some probing
82 // during search for instance.
83 void MaybeEnablePhaseSaving(bool save_phase) {
84 maybe_enable_phase_saving_ = save_phase;
85 }
86
87 // Gives a hint so the solver tries to find a solution with the given literal
88 // set to true. Currently this take precedence over the phase saving heuristic
89 // and a variable with a preference will always be branched on according to
90 // this preference.
91 //
92 // The weight is used as a tie-breaker between variable with the same
93 // activities. Larger weight will be selected first. A weight of zero is the
94 // default value for the other variables.
95 //
96 // Note(user): Having a lot of different weights may slow down the priority
97 // queue operations if there is millions of variables.
98 void SetAssignmentPreference(Literal literal, float weight);
99
100 // Returns the vector of the current assignment preferences.
101 std::vector<std::pair<Literal, float>> AllPreferences() const;
102
103 // Returns the current activity of a BooleanVariable.
104 double Activity(Literal l) const {
105 if (l.Variable() < activities_.size()) return activities_[l.Variable()];
106 return 0.0;
107 }
108
109 // Like SetAssignmentPreference() but it can be overridden by phase-saving.
111 if (trail_.Assignment().VariableIsAssigned(l.Variable())) return;
112 has_target_polarity_[l.Variable()] = true;
113 target_polarity_[l.Variable()] = var_polarity_[l.Variable()] =
114 l.IsPositive();
115 best_partial_assignment_.push_back(l);
116 target_length_++;
117 }
118 absl::Span<const Literal> GetBestPartialAssignment() const {
119 return best_partial_assignment_;
120 }
122 target_length_ = 0;
123 has_target_polarity_.assign(has_target_polarity_.size(), false);
124 best_partial_assignment_.clear();
125 }
126
127 // Increases activities of variables in the best partial assignment to ensure
128 // they are branched on first in the same order until the next conflict.
129 // Activities before this call are scaled to become disambiguation terms.
130 // Future conflicts will bump activity by the largest increase applied by this
131 // method.
132 // This acts as a soft-reset of the decision policy, useful when exploring a
133 // new region of the search space.
135
136 private:
137 // Computes an initial variable ordering.
138 void InitializeVariableOrdering();
139
140 // Rescales activity value of all variables when one of them reached the max.
141 void RescaleVariableActivities(double scaling_factor);
142
143 // Reinitializes the initial polarity of all the variables with an index
144 // greater than or equal to the given one.
145 void ResetInitialPolarity(int from, bool inverted = false);
146
147 // Code used for resetting the initial polarity at the beginning of each
148 // phase.
149 void RephaseIfNeeded();
150 void UseLongestAssignmentAsInitialPolarity();
151 void FlipCurrentPolarity();
152 void RandomizeCurrentPolarity();
153
154 // This one returns false if there is no such solution to use.
155 bool UseLsSolutionAsInitialPolarity();
156
157 // Adds the given variable to var_ordering_ or updates its priority if it is
158 // already present.
159 void PqInsertOrUpdate(BooleanVariable var);
160
161 // Singleton model objects.
162 const SatParameters& parameters_;
163 const Trail& trail_;
164 ModelRandomGenerator* random_;
165
166 // TODO(user): This is in term of proto indices. Ideally we would need
167 // CpModelMapping to map that to Booleans but this currently lead to cyclic
168 // dependencies. For now we just assume one to one correspondence for the
169 // first entries. This will only work on pure Boolean problems.
171
172 // Variable ordering (priority will be adjusted dynamically). queue_elements_
173 // holds the elements used by var_ordering_ (it uses pointers).
174 //
175 // Note that we recover the variable that a WeightedVarQueueElement refers to
176 // by its position in the queue_elements_ vector, and we can recover the later
177 // using (pointer - &queue_elements_[0]).
178 struct WeightedVarQueueElement {
179 // Interface for the IntegerPriorityQueue.
180 int Index() const { return var.value(); }
181
182 // Priority order. The IntegerPriorityQueue returns the largest element
183 // first.
184 //
185 // Note(user): We used to also break ties using the variable index, however
186 // this has two drawbacks:
187 // - On problem with many variables, this slow down quite a lot the priority
188 // queue operations (which do as little work as possible and hence benefit
189 // from having the majority of elements with a priority of 0).
190 // - It seems to be a bad heuristics. One reason could be that the priority
191 // queue will automatically diversify the choice of the top variables
192 // amongst the ones with the same priority.
193 //
194 // Note(user): For the same reason as explained above, it is probably a good
195 // idea not to have too many different values for the tie_breaker field. I
196 // am not even sure we should have such a field...
197 bool operator<(const WeightedVarQueueElement& other) const {
198 return weight < other.weight ||
199 (weight == other.weight && (tie_breaker < other.tie_breaker));
200 }
201
202 BooleanVariable var;
203 float tie_breaker;
204
205 // TODO(user): Experiment with float. In the rest of the code, we use
206 // double, but maybe we don't need that much precision. Using float here may
207 // save memory and make the PQ operations faster.
208 double weight;
209 };
210 static_assert(sizeof(WeightedVarQueueElement) == 16,
211 "ERROR_WeightedVarQueueElement_is_not_well_compacted");
212
213 bool var_ordering_is_initialized_ = false;
214 IntegerPriorityQueue<WeightedVarQueueElement> var_ordering_;
215
216 // This is used for the branching heuristic described in "Learning Rate Based
217 // Branching Heuristic for SAT solvers", J.H.Liang, V. Ganesh, P. Poupart,
218 // K.Czarnecki, SAT 2016.
219 //
220 // The entries are sorted by trail index, and one can get the number of
221 // conflicts during which a variable at a given trail index i was assigned by
222 // summing the entry.count for all entries with a trail index greater than i.
223 struct NumConflictsStackEntry {
224 int trail_index;
225 int64_t count;
226 };
227 int64_t num_conflicts_ = 0;
228 std::vector<NumConflictsStackEntry> num_conflicts_stack_;
229
230 // Whether the priority of the given variable needs to be updated in
231 // var_ordering_. Note that this is only accessed for assigned variables and
232 // that for efficiency it is indexed by trail indices. If
233 // pq_need_update_for_var_at_trail_index_[trail_->Info(var).trail_index] is
234 // true when we untrail var, then either var need to be inserted in the queue,
235 // or we need to notify that its priority has changed.
236 BitQueue64 pq_need_update_for_var_at_trail_index_;
237
238 // Increment used to bump the variable activities.
239 double variable_activity_increment_ = 1.0;
240
241 // Stores variable activity and the number of time each variable was "bumped".
242 // The later is only used with the ERWA heuristic.
243 util_intops::StrongVector<BooleanVariable, double> activities_;
244 util_intops::StrongVector<BooleanVariable, float> tie_breakers_;
245 util_intops::StrongVector<BooleanVariable, int64_t> num_bumps_;
246
247 // If the polarity if forced (externally) we always use this first.
248 util_intops::StrongVector<BooleanVariable, bool> has_forced_polarity_;
249 util_intops::StrongVector<BooleanVariable, bool> forced_polarity_;
250
251 // If we are in a stable phase, we follow the current target.
252 bool in_stable_phase_ = false;
253 int target_length_ = 0;
254 util_intops::StrongVector<BooleanVariable, bool> has_target_polarity_;
255 util_intops::StrongVector<BooleanVariable, bool> target_polarity_;
256
257 // Otherwise we follow var_polarity_ which is reset at the beginning of
258 // each new polarity phase. This is also overwritten by phase saving.
259 // Each phase last for an arithmetically increasing number of conflicts.
260 util_intops::StrongVector<BooleanVariable, bool> var_polarity_;
261 bool maybe_enable_phase_saving_ = true;
262 int64_t polarity_phase_ = 0;
263 int64_t num_conflicts_until_rephase_ = 1000;
264
265 // The longest partial assignment since the last reset.
266 std::vector<Literal> best_partial_assignment_;
267
268 // Used in InitializeVariableOrdering().
269 std::vector<BooleanVariable> tmp_variables_;
270};
271
272} // namespace sat
273} // namespace operations_research
274
275#endif // ORTOOLS_SAT_SAT_DECISION_H_
BooleanVariable Variable() const
Definition sat_base.h:88
void BumpVariableActivities(absl::Span< const Literal > literals)
std::vector< std::pair< Literal, float > > AllPreferences() const
void SetAssignmentPreference(Literal literal, float weight)
absl::Span< const Literal > GetBestPartialAssignment() const
OR-Tools root namespace.