OR-Tools  9.1
lb_tree_search.cc
Go to the documentation of this file.
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
15
16#include <cstdint>
17
19
20namespace operations_research {
21namespace sat {
22
24 : time_limit_(model->GetOrCreate<TimeLimit>()),
25 random_(model->GetOrCreate<ModelRandomGenerator>()),
26 sat_solver_(model->GetOrCreate<SatSolver>()),
27 integer_trail_(model->GetOrCreate<IntegerTrail>()),
28 shared_response_(model->GetOrCreate<SharedResponseManager>()),
29 sat_decision_(model->GetOrCreate<SatDecisionPolicy>()),
30 search_helper_(model->GetOrCreate<IntegerSearchHelper>()) {
31 // We should create this class only in the presence of an objective.
32 //
33 // TODO(user): Starts with an initial variable score for all variable in
34 // the objective at their minimum value? this should emulate the first step of
35 // the core approach and gives a similar bound.
36 const ObjectiveDefinition* objective = model->Get<ObjectiveDefinition>();
37 CHECK(objective != nullptr);
38 objective_var_ = objective->objective_var;
39
40 // We use the normal SAT search but we will bump the variable activity
41 // slightly differently. In addition to the conflicts, we also bump it each
42 // time the objective lower bound increase in a sub-node.
43 search_heuristic_ =
45 model->GetOrCreate<SearchHeuristics>()->fixed_search});
46}
47
48bool LbTreeSearch::NodeImprovesLowerBound(const Node& node) {
49 return node.objective_lb >
50 integer_trail_->LevelZeroLowerBound(objective_var_);
51}
52
54 const std::function<void()>& feasible_solution_observer) {
55 if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
56 return sat_solver_->UnsatStatus();
57 }
58
59 // We currently restart the search tree from scratch a few time. This is to
60 // allow our "pseudo-cost" to kick in and experimentally result in smaller
61 // trees down the road.
62 //
63 // TODO(user): a strong branching initial start, or allowing a few decision
64 // per nodes might be a better approach.
65 //
66 // TODO(user): It would also be cool to exploit the reason for the LB increase
67 // even more.
68 int64_t restart = 100;
69 int64_t num_restart = 1;
70 const int kNumRestart = 10;
71
72 while (!time_limit_->LimitReached() && !shared_response_->ProblemIsSolved()) {
73 // Each time we are back here, we bump the activities of the variable that
74 // are part of the objective lower bound reason.
75 //
76 // TODO(user): This is slightly different than bumping each time we
77 // push a decision that result in an LB increase, I am not sure why.
78 if (integer_trail_->LowerBound(objective_var_) >
79 integer_trail_->LevelZeroLowerBound(objective_var_)) {
80 std::vector<Literal> reason =
82 objective_var_, integer_trail_->LowerBound(objective_var_)));
83 sat_decision_->BumpVariableActivities(reason);
84 sat_decision_->UpdateVariableActivityIncrement();
85
86 // Optimization. Record what level is needed for this reason and try to
87 // reduce the search tree if this node decision could have been taken
88 // earlier.
89 const int current_level = sat_solver_->CurrentDecisionLevel();
90 if (current_branch_.size() == current_level) {
91 // TODO(user): We should probably expand the reason.
92 int max_level = 0;
93 Node& node = nodes_[current_branch_.back()];
94 for (const Literal l : reason) {
95 if (l.Variable() == node.literal.Variable()) continue;
96 max_level = std::max<int>(
97 max_level, sat_solver_->LiteralTrail().Info(l.Variable()).level);
98 }
99 if (sat_solver_->Assignment().LiteralIsTrue(node.literal)) {
100 node.true_level = std::min(node.true_level, max_level);
101 } else {
102 CHECK(sat_solver_->Assignment().LiteralIsFalse(node.literal));
103 node.false_level = std::min(node.false_level, max_level);
104 }
105 const int level = std::max(node.true_level, node.false_level);
106 if (level < current_level - 1) {
107 // We Skip a part of the tree and connect directly "ancestor" to
108 // "node".
109 if (level > 0) {
110 Node& ancestor = nodes_[current_branch_[level - 1]];
111 if (sat_solver_->Assignment().LiteralIsTrue(ancestor.literal)) {
112 ancestor.true_child = current_branch_.back();
113 ancestor.UpdateTrueObjective(node.objective_lb);
114 } else {
115 CHECK(sat_solver_->Assignment().LiteralIsFalse(ancestor.literal));
116 ancestor.false_child = current_branch_.back();
117 ancestor.UpdateFalseObjective(node.objective_lb);
118 }
119 current_branch_.resize(level);
120 } else {
121 const Node copy = node;
122 nodes_ = {copy};
123 current_branch_ = {0};
124 }
125 sat_solver_->Backtrack(level);
126 if (!sat_solver_->FinishPropagation()) {
127 return sat_solver_->UnsatStatus();
128 }
129 }
130 }
131 }
132
133 // Forget the whole tree and restart?
134 if (nodes_.size() > num_restart * restart && num_restart < kNumRestart) {
135 nodes_.clear();
136 current_branch_.clear();
137 if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
138 return sat_solver_->UnsatStatus();
139 }
140 ++num_restart;
141 }
142
143 if (!search_helper_->BeforeTakingDecision()) {
144 return sat_solver_->UnsatStatus();
145 }
146
147 // Backtrack if needed.
148 //
149 // Our algorithm stop exploring a branch as soon as its objective lower
150 // bound is greater than the root lower bound. We then backtrack to the
151 // first node in the branch that is not yet closed under this bound.
152 //
153 // TODO(user): If we remember how far we can backjump for both true/false
154 // branch, we could be more efficient.
155 while (current_branch_.size() > sat_solver_->CurrentDecisionLevel() + 1 ||
156 (current_branch_.size() > 1 &&
157 NodeImprovesLowerBound(nodes_[current_branch_.back()]))) {
158 const int child = current_branch_.back();
159 current_branch_.pop_back();
160
161 // By construction, we never backtrack over the root node here.
162 CHECK(!current_branch_.empty());
163 Node& node = nodes_[current_branch_.back()];
164 if (node.true_child == child) {
165 node.UpdateTrueObjective(nodes_[child].objective_lb);
166 } else {
167 CHECK_EQ(node.false_child, child);
168 node.UpdateFalseObjective(nodes_[child].objective_lb);
169 }
170 }
171
172 // Backtrack the solver.
173 sat_solver_->Backtrack(
174 std::max(0, static_cast<int>(current_branch_.size()) - 1));
175 if (!sat_solver_->FinishPropagation()) {
176 return sat_solver_->UnsatStatus();
177 }
178
179 // If we are back to the root node, update the global objective LB.
180 if (sat_solver_->CurrentDecisionLevel() == 0 && !current_branch_.empty()) {
181 if (NodeImprovesLowerBound(nodes_[current_branch_[0]])) {
182 shared_response_->UpdateInnerObjectiveBounds(
183 absl::StrCat("lb_tree_search #nodes:", nodes_.size()),
184 nodes_[current_branch_[0]].objective_lb,
185 integer_trail_->UpperBound(objective_var_));
186 if (!integer_trail_->Enqueue(
188 objective_var_, nodes_[current_branch_[0]].objective_lb),
189 {}, {})) {
191 }
192 if (!sat_solver_->FinishPropagation()) {
193 return sat_solver_->UnsatStatus();
194 }
195 }
196 }
197
198 // Follow the branch with lowest objective.
199 while (current_branch_.size() == sat_solver_->CurrentDecisionLevel() + 1) {
200 Node& node = nodes_[current_branch_.back()];
201 node.objective_lb = std::max(node.objective_lb,
202 integer_trail_->LowerBound(objective_var_));
203 node.UpdateTrueObjective(node.objective_lb);
204 node.UpdateFalseObjective(node.objective_lb);
205 if (NodeImprovesLowerBound(node)) break;
206
207 // This will be set to the next index.
208 int n;
209
210 // If the variable is already fixed, we bypass the node and connect
211 // its parent directly to the relevant child.
212 if (sat_solver_->Assignment().LiteralIsAssigned(node.literal)) {
213 IntegerValue new_lb;
214 if (sat_solver_->Assignment().LiteralIsTrue(node.literal)) {
215 n = node.true_child;
216 new_lb = node.true_objective;
217 } else {
218 n = node.false_child;
219 new_lb = node.false_objective;
220 }
221
222 // We jump directly to the subnode.
223 current_branch_.pop_back();
224
225 // Else we will change the root.
226 if (!current_branch_.empty()) {
227 const int parent = current_branch_.back();
228 if (sat_solver_->Assignment().LiteralIsTrue(nodes_[parent].literal)) {
229 nodes_[parent].true_child = n;
230 nodes_[parent].UpdateTrueObjective(new_lb);
231 } else {
232 CHECK(sat_solver_->Assignment().LiteralIsFalse(
233 nodes_[parent].literal));
234 nodes_[parent].false_child = n;
235 nodes_[parent].UpdateFalseObjective(new_lb);
236 }
237 if (NodeImprovesLowerBound(nodes_[parent])) break;
238 }
239 } else {
240 // If both lower bound are the same, we pick a random sub-branch.
241 bool choose_true = node.true_objective < node.false_objective;
242 if (node.true_objective == node.false_objective) {
243 choose_true = absl::Bernoulli(*random_, 0.5);
244 }
245 if (choose_true) {
246 DCHECK_EQ(node.true_objective,
247 integer_trail_->LevelZeroLowerBound(objective_var_));
248 n = node.true_child;
249 search_helper_->TakeDecision(node.literal);
250 } else {
251 DCHECK_EQ(node.false_objective,
252 integer_trail_->LevelZeroLowerBound(objective_var_));
253 n = node.false_child;
254 search_helper_->TakeDecision(node.literal.Negated());
255 }
256
257 // Conflict?
258 if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
259 if (choose_true) {
260 node.UpdateTrueObjective(kMaxIntegerValue);
261 } else {
262 node.UpdateFalseObjective(kMaxIntegerValue);
263 }
264 break;
265 }
266
267 if (choose_true) {
268 node.UpdateTrueObjective(integer_trail_->LowerBound(objective_var_));
269 } else {
270 node.UpdateFalseObjective(integer_trail_->LowerBound(objective_var_));
271 }
272 if (NodeImprovesLowerBound(node)) break;
273 if (integer_trail_->LowerBound(objective_var_) >
274 integer_trail_->LevelZeroLowerBound(objective_var_)) {
275 break;
276 }
277 }
278
279 if (n < nodes_.size()) {
280 current_branch_.push_back(n);
281 } else {
282 break;
283 }
284 }
285
286 // If conflict or the objective lower bound increased, we will backtrack.
287 if (current_branch_.size() != sat_solver_->CurrentDecisionLevel() ||
288 (!current_branch_.empty() &&
289 NodeImprovesLowerBound(nodes_[current_branch_.back()]))) {
290 continue;
291 }
292
293 // This test allow to not take a decision when the branch is already closed
294 // (i.e. the true branch or false branch lb is high enough). Adding it
295 // basically changes if we take the decision later when we explore the
296 // branch or right now.
297 //
298 // I feel taking it later is better. It also avoid creating uneeded nodes.
299 // It does change the behavior on a few problem though. For instance on
300 // irp.mps.gz, the search works better without this, whatever the random
301 // seed. Not sure why, maybe it creates more diversity?
302 //
303 // Another difference is that if the search is done and we have a feasible
304 // solution, we will not report it because of this test (except if we are
305 // at the optimal).
306 if (integer_trail_->LowerBound(objective_var_) >
307 integer_trail_->LevelZeroLowerBound(objective_var_)) {
308 continue;
309 }
310
311 // Increase the size of the tree by exploring a new decision.
312 const LiteralIndex decision =
313 search_helper_->GetDecision(search_heuristic_);
314
315 // No new decision: search done.
316 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
317 if (decision == kNoLiteralIndex) {
318 feasible_solution_observer();
319 continue;
320 }
321
322 // Create a new node.
323 // Note that the decision will be pushed to the solver on the next loop.
324 const int n = nodes_.size();
325 nodes_.emplace_back(Literal(decision),
326 integer_trail_->LowerBound(objective_var_));
327 if (!current_branch_.empty()) {
328 const int parent = current_branch_.back();
329 if (sat_solver_->Assignment().LiteralIsTrue(nodes_[parent].literal)) {
330 nodes_[parent].true_child = n;
331 nodes_[parent].UpdateTrueObjective(nodes_.back().objective_lb);
332 } else {
333 CHECK(sat_solver_->Assignment().LiteralIsFalse(nodes_[parent].literal));
334 nodes_[parent].false_child = n;
335 nodes_[parent].UpdateFalseObjective(nodes_.back().objective_lb);
336 }
337 }
338 current_branch_.push_back(n);
339 }
340
342}
343
344} // namespace sat
345} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:491
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:533
LiteralIndex GetDecision(const std::function< BooleanOrIntegerLiteral()> &f)
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1028
std::vector< Literal > ReasonFor(IntegerLiteral literal) const
Definition: integer.cc:1617
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1349
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1407
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1345
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
void BumpVariableActivities(const std::vector< Literal > &literals)
const VariablesAssignment & Assignment() const
Definition: sat_solver.h:363
const Trail & LiteralTrail() const
Definition: sat_solver.h:362
void Backtrack(int target_level)
Definition: sat_solver.cc:889
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:382
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:154
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:151
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:148
GRBmodel * model
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()> > heuristics)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
Collection of objects used to extend the Constraint Solver library.
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1309