OR-Tools  9.2
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 
20 namespace operations_research {
21 namespace sat {
22 
24  : time_limit_(model->GetOrCreate<TimeLimit>()),
25  random_(model->GetOrCreate<ModelRandomGenerator>()),
26  sat_solver_(model->GetOrCreate<SatSolver>()),
27  integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
28  integer_trail_(model->GetOrCreate<IntegerTrail>()),
29  shared_response_(model->GetOrCreate<SharedResponseManager>()),
30  sat_decision_(model->GetOrCreate<SatDecisionPolicy>()),
31  search_helper_(model->GetOrCreate<IntegerSearchHelper>()) {
32  // We should create this class only in the presence of an objective.
33  //
34  // TODO(user): Starts with an initial variable score for all variable in
35  // the objective at their minimum value? this should emulate the first step of
36  // the core approach and gives a similar bound.
37  const ObjectiveDefinition* objective = model->Get<ObjectiveDefinition>();
38  CHECK(objective != nullptr);
39  objective_var_ = objective->objective_var;
40 
41  // Identify an LP with the same objective variable.
42  //
43  // TODO(user): if we have many independent LP, this will find nothing.
46  if (lp->ObjectiveVariable() == objective_var_) {
47  lp_constraint_ = lp;
48  }
49  }
50 
51  // We use the normal SAT search but we will bump the variable activity
52  // slightly differently. In addition to the conflicts, we also bump it each
53  // time the objective lower bound increase in a sub-node.
54  search_heuristic_ =
56  model->GetOrCreate<SearchHeuristics>()->fixed_search});
57 }
58 
59 void LbTreeSearch::UpdateParentObjective(int level) {
60  CHECK_GE(level, 0);
61  CHECK_LT(level, current_branch_.size());
62  if (level == 0) return;
63  const NodeIndex parent_index = current_branch_[level - 1];
64  Node& parent = nodes_[parent_index];
65  const NodeIndex child_index = current_branch_[level];
66  const Node& child = nodes_[child_index];
67  if (parent.true_child == child_index) {
68  parent.UpdateTrueObjective(child.MinObjective());
69  } else {
70  CHECK_EQ(parent.false_child, child_index);
71  parent.UpdateFalseObjective(child.MinObjective());
72  }
73 }
74 
75 void LbTreeSearch::UpdateObjectiveFromParent(int level) {
76  CHECK_GE(level, 0);
77  CHECK_LT(level, current_branch_.size());
78  if (level == 0) return;
79  const NodeIndex parent_index = current_branch_[level - 1];
80  const Node& parent = nodes_[parent_index];
81  CHECK_GE(parent.MinObjective(), current_objective_lb_);
82  const NodeIndex child_index = current_branch_[level];
83  Node& child = nodes_[child_index];
84  if (parent.true_child == child_index) {
85  child.UpdateObjective(parent.true_objective);
86  } else {
87  CHECK_EQ(parent.false_child, child_index);
88  child.UpdateObjective(parent.false_objective);
89  }
90 }
91 
92 void LbTreeSearch::DebugDisplayTree(NodeIndex root) const {
93  int num_nodes = 0;
94  const IntegerValue root_lb = nodes_[root].MinObjective();
95  const auto shifted_lb = [root_lb](IntegerValue lb) {
96  return std::max<int64_t>(0, (lb - root_lb).value());
97  };
98 
99  absl::StrongVector<NodeIndex, int> level(nodes_.size(), 0);
100  std::vector<NodeIndex> to_explore = {root};
101  while (!to_explore.empty()) {
102  NodeIndex n = to_explore.back();
103  to_explore.pop_back();
104 
105  ++num_nodes;
106  const Node& node = nodes_[n];
107 
108  std::string s(level[n], ' ');
109  absl::StrAppend(&s, "#", n.value());
110 
111  if (node.true_child < nodes_.size()) {
112  absl::StrAppend(&s, " [t:#", node.true_child.value(), " ",
113  shifted_lb(node.true_objective), "]");
114  to_explore.push_back(node.true_child);
115  level[node.true_child] = level[n] + 1;
116  } else {
117  absl::StrAppend(&s, " [t:## ", shifted_lb(node.true_objective), "]");
118  }
119  if (node.false_child < nodes_.size()) {
120  absl::StrAppend(&s, " [f:#", node.false_child.value(), " ",
121  shifted_lb(node.false_objective), "]");
122  to_explore.push_back(node.false_child);
123  level[node.false_child] = level[n] + 1;
124  } else {
125  absl::StrAppend(&s, " [f:## ", shifted_lb(node.false_objective), "]");
126  }
127  LOG(INFO) << s;
128  }
129  LOG(INFO) << "num_nodes: " << num_nodes;
130 }
131 
133  const std::function<void()>& feasible_solution_observer) {
134  if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
135  return sat_solver_->UnsatStatus();
136  }
137 
138  // We currently restart the search tree from scratch a few time. This is to
139  // allow our "pseudo-cost" to kick in and experimentally result in smaller
140  // trees down the road.
141  //
142  // TODO(user): a strong branching initial start, or allowing a few decision
143  // per nodes might be a better approach.
144  //
145  // TODO(user): It would also be cool to exploit the reason for the LB increase
146  // even more.
147  int64_t restart = 100;
148  int64_t num_restart = 1;
149  const int kNumRestart = 10;
150 
151  while (!time_limit_->LimitReached() && !shared_response_->ProblemIsSolved()) {
152  // This is the current bound we try to improve. We cache it here to avoid
153  // getting the lock many times and it is also easier to follow the code if
154  // this is assumed constant for one iteration.
155  current_objective_lb_ = shared_response_->GetInnerObjectiveLowerBound();
156 
157  // Propagate upward in the tree the new objective lb.
158  if (!current_branch_.empty()) {
159  // Our branch is always greater or equal to the level.
160  // We increase the objective_lb of the current node if needed.
161  {
162  const int current_level = sat_solver_->CurrentDecisionLevel();
163  CHECK_GE(current_branch_.size(), current_level);
164  for (int i = 0; i < current_level; ++i) {
165  CHECK(sat_solver_->Assignment().LiteralIsAssigned(
166  nodes_[current_branch_[i]].literal));
167  }
168  if (current_level < current_branch_.size()) {
169  nodes_[current_branch_[current_level]].UpdateObjective(
170  integer_trail_->LowerBound(objective_var_));
171  }
172 
173  // Minor optim: sometimes, because of the LP and cuts, the reason for
174  // objective_var_ only contains lower level literals, so we can exploit
175  // that.
176  //
177  // TODO(user): No point checking that if the objective lb wasn't
178  // assigned at this level.
179  //
180  // TODO(user): Exploit the reasons further.
181  if (integer_trail_->LowerBound(objective_var_) >
182  integer_trail_->LevelZeroLowerBound(objective_var_)) {
183  const std::vector<Literal> reason =
185  objective_var_, integer_trail_->LowerBound(objective_var_)));
186  int max_level = 0;
187  for (const Literal l : reason) {
188  max_level = std::max<int>(
189  max_level,
190  sat_solver_->LiteralTrail().Info(l.Variable()).level);
191  }
192  if (max_level < current_level) {
193  nodes_[current_branch_[max_level]].UpdateObjective(
194  integer_trail_->LowerBound(objective_var_));
195  }
196  }
197  }
198 
199  // Propagate upward and then forward any new bounds.
200  for (int level = current_branch_.size(); --level > 0;) {
201  UpdateParentObjective(level);
202  }
203  nodes_[current_branch_[0]].UpdateObjective(current_objective_lb_);
204  for (int level = 1; level < current_branch_.size(); ++level) {
205  UpdateObjectiveFromParent(level);
206  }
207 
208  // If the root lb increased, update global shared objective lb.
209  const IntegerValue bound = nodes_[current_branch_[0]].MinObjective();
210  if (bound > current_objective_lb_) {
211  shared_response_->UpdateInnerObjectiveBounds(
212  absl::StrCat("lb_tree_search #nodes:", nodes_.size(),
213  " #rc:", num_rc_detected_),
214  bound, integer_trail_->LevelZeroUpperBound(objective_var_));
215  current_objective_lb_ = bound;
216  if (VLOG_IS_ON(2)) DebugDisplayTree(current_branch_[0]);
217  }
218  }
219 
220  // Each time we are back here, we bump the activities of the variable that
221  // are part of the objective lower bound reason.
222  //
223  // Note that this is why we prefer not to increase the lower zero lower
224  // bound of objective_var_ with the tree root lower bound, so we can exploit
225  // more reasons.
226  //
227  // TODO(user): This is slightly different than bumping each time we
228  // push a decision that result in an LB increase. This is also called on
229  // backjump for instance.
230  if (integer_trail_->LowerBound(objective_var_) >
231  integer_trail_->LevelZeroLowerBound(objective_var_)) {
232  std::vector<Literal> reason =
234  objective_var_, integer_trail_->LowerBound(objective_var_)));
235  sat_decision_->BumpVariableActivities(reason);
236  sat_decision_->UpdateVariableActivityIncrement();
237  }
238 
239  // Forget the whole tree and restart?
240  if (nodes_.size() > num_restart * restart && num_restart < kNumRestart) {
241  nodes_.clear();
242  current_branch_.clear();
243  if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
244  return sat_solver_->UnsatStatus();
245  }
246  ++num_restart;
247  }
248 
249  // Backtrack if needed.
250  //
251  // Our algorithm stop exploring a branch as soon as its objective lower
252  // bound is greater than the root lower bound. We then backtrack to the
253  // first node in the branch that is not yet closed under this bound.
254  //
255  // TODO(user): If we remember how far we can backjump for both true/false
256  // branch, we could be more efficient.
257  while (current_branch_.size() > sat_solver_->CurrentDecisionLevel() + 1 ||
258  (current_branch_.size() > 1 &&
259  nodes_[current_branch_.back()].MinObjective() >
260  current_objective_lb_)) {
261  current_branch_.pop_back();
262  }
263 
264  // Backtrack the solver.
265  sat_solver_->Backtrack(
266  std::max(0, static_cast<int>(current_branch_.size()) - 1));
267  if (!sat_solver_->FinishPropagation()) {
268  return sat_solver_->UnsatStatus();
269  }
270 
271  // This will import other workers bound if we are back to level zero.
272  if (!search_helper_->BeforeTakingDecision()) {
273  return sat_solver_->UnsatStatus();
274  }
275 
276  // Dive: Follow the branch with lowest objective.
277  // Note that we do not creates new nodes here.
278  while (current_branch_.size() == sat_solver_->CurrentDecisionLevel() + 1) {
279  const int level = current_branch_.size() - 1;
280  CHECK_EQ(level, sat_solver_->CurrentDecisionLevel());
281  Node& node = nodes_[current_branch_[level]];
282  node.UpdateObjective(std::max(
283  current_objective_lb_, integer_trail_->LowerBound(objective_var_)));
284  if (node.MinObjective() > current_objective_lb_) {
285  break;
286  }
287  CHECK_EQ(node.MinObjective(), current_objective_lb_) << level;
288 
289  // This will be set to the next node index.
290  NodeIndex n;
291 
292  // If the variable is already fixed, we bypass the node and connect
293  // its parent directly to the relevant child.
294  if (sat_solver_->Assignment().LiteralIsAssigned(node.literal)) {
295  IntegerValue new_lb;
296  if (sat_solver_->Assignment().LiteralIsTrue(node.literal)) {
297  n = node.true_child;
298  new_lb = node.true_objective;
299  } else {
300  n = node.false_child;
301  new_lb = node.false_objective;
302  }
303 
304  // We jump directly to the subnode.
305  // Else we will change the root.
306  current_branch_.pop_back();
307  if (!current_branch_.empty()) {
308  const NodeIndex parent = current_branch_.back();
309  if (sat_solver_->Assignment().LiteralIsTrue(nodes_[parent].literal)) {
310  nodes_[parent].true_child = n;
311  nodes_[parent].UpdateTrueObjective(new_lb);
312  } else {
313  CHECK(sat_solver_->Assignment().LiteralIsFalse(
314  nodes_[parent].literal));
315  nodes_[parent].false_child = n;
316  nodes_[parent].UpdateFalseObjective(new_lb);
317  }
318  if (nodes_[parent].MinObjective() > current_objective_lb_) break;
319  }
320  } else {
321  // If both lower bound are the same, we pick a random sub-branch.
322  bool choose_true = node.true_objective < node.false_objective;
323  if (node.true_objective == node.false_objective) {
324  choose_true = absl::Bernoulli(*random_, 0.5);
325  }
326  if (choose_true) {
327  n = node.true_child;
328  search_helper_->TakeDecision(node.literal);
329  } else {
330  n = node.false_child;
331  search_helper_->TakeDecision(node.literal.Negated());
332  }
333 
334  // Conflict?
335  if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
336  if (choose_true) {
337  node.UpdateTrueObjective(kMaxIntegerValue);
338  } else {
339  node.UpdateFalseObjective(kMaxIntegerValue);
340  }
341  break;
342  }
343 
344  // Update the proper field and abort the dive if we crossed the
345  // threshold.
346  const IntegerValue lb = integer_trail_->LowerBound(objective_var_);
347  if (choose_true) {
348  node.UpdateTrueObjective(lb);
349  } else {
350  node.UpdateFalseObjective(lb);
351  }
352  if (lb > current_objective_lb_) break;
353  }
354 
355  if (n < nodes_.size()) {
356  current_branch_.push_back(n);
357  } else {
358  break;
359  }
360  }
361 
362  // If a conflict occurred, we will backtrack.
363  if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
364  continue;
365  }
366 
367  // This test allow to not take a decision when the branch is already closed
368  // (i.e. the true branch or false branch lb is high enough). Adding it
369  // basically changes if we take the decision later when we explore the
370  // branch or right now.
371  //
372  // I feel taking it later is better. It also avoid creating uneeded nodes.
373  // It does change the behavior on a few problem though. For instance on
374  // irp.mps.gz, the search works better without this, whatever the random
375  // seed. Not sure why, maybe it creates more diversity?
376  //
377  // Another difference is that if the search is done and we have a feasible
378  // solution, we will not report it because of this test (except if we are
379  // at the optimal).
380  if (integer_trail_->LowerBound(objective_var_) > current_objective_lb_) {
381  continue;
382  }
383 
384  // Increase the size of the tree by exploring a new decision.
385  const LiteralIndex decision =
386  search_helper_->GetDecision(search_heuristic_);
387 
388  // No new decision: search done.
389  if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
390  if (decision == kNoLiteralIndex) {
391  feasible_solution_observer();
392  continue;
393  }
394 
395  // Create a new node.
396  // Note that the decision will be pushed to the solver on the next loop.
397  const NodeIndex n(nodes_.size());
398  nodes_.emplace_back(Literal(decision),
399  std::max(current_objective_lb_,
400  integer_trail_->LowerBound(objective_var_)));
401  if (!current_branch_.empty()) {
402  const NodeIndex parent = current_branch_.back();
403  if (sat_solver_->Assignment().LiteralIsTrue(nodes_[parent].literal)) {
404  nodes_[parent].true_child = n;
405  nodes_[parent].UpdateTrueObjective(nodes_.back().MinObjective());
406  } else {
407  CHECK(sat_solver_->Assignment().LiteralIsFalse(nodes_[parent].literal));
408  nodes_[parent].false_child = n;
409  nodes_[parent].UpdateFalseObjective(nodes_.back().MinObjective());
410  }
411  }
412  current_branch_.push_back(n);
413 
414  // Looking at the reduced costs, we can already have a bound for one of the
415  // branch. Increasing the corresponding objective can save some branches,
416  // and also allow for a more incremental LP solving since we do less back
417  // and forth.
418  //
419  // TODO(user): The code to recover that is a bit convoluted. Alternatively
420  // Maybe we should do a "fast" propagation without the LP in each branch.
421  // That will work as long as we keep these optimal LP constraints around
422  // and propagate them.
423  //
424  // TODO(user): Incorporate this in the heuristic so we choose more Boolean
425  // inside these LP explanations?
426  if (lp_constraint_ != nullptr) {
427  // Note that this return literal EQUIVALENT to the decision, not just
428  // implied by it. We need that for correctness.
429  int num_tests = 0;
430  for (const IntegerLiteral integer_literal :
431  integer_encoder_->GetIntegerLiterals(Literal(decision))) {
432  if (integer_trail_->IsCurrentlyIgnored(integer_literal.var)) continue;
433 
434  // To avoid bad corner case. Not sure it ever triggers.
435  if (++num_tests > 10) break;
436 
437  // TODO(user): we could consider earlier constraint instead of just
438  // looking at the last one, but experiments didn't really show a big
439  // gain.
440  const auto& cts = lp_constraint_->OptimalConstraints();
441  if (cts.empty()) continue;
442 
443  const std::unique_ptr<IntegerSumLE>& rc = cts.back();
444  const std::pair<IntegerValue, IntegerValue> bounds =
445  rc->ConditionalLb(integer_literal, objective_var_);
446  Node& node = nodes_[n];
447  if (bounds.first > node.false_objective) {
448  ++num_rc_detected_;
449  node.UpdateFalseObjective(bounds.first);
450  }
451  if (bounds.second > node.true_objective) {
452  ++num_rc_detected_;
453  node.UpdateTrueObjective(bounds.second);
454  }
455  }
456  }
457  }
458 
460 }
461 
462 } // namespace sat
463 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:495
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
int64_t bound
const InlinedIntegerLiteralVector & GetIntegerLiterals(Literal lit) const
Definition: integer.h:463
#define CHECK_GE(val1, val2)
Definition: base/logging.h:706
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1435
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:149
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:152
#define LOG(severity)
Definition: base/logging.h:420
GRBmodel * model
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::vector< Literal > ReasonFor(IntegerLiteral literal) const
Definition: integer.cc:1616
const std::vector< std::unique_ptr< IntegerSumLE > > & OptimalConstraints() const
#define CHECK_LT(val1, val2)
Definition: base/logging.h:705
void emplace_back(Args &&... args)
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
int64_t max
Definition: alldiff_cst.cc:140
bool empty() const
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1524
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:155
void push_back(const value_type &x)
void Backtrack(int target_level)
Definition: sat_solver.cc:889
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
size_type size() const
const VariablesAssignment & Assignment() const
Definition: sat_solver.h:363
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:534
void BumpVariableActivities(const std::vector< Literal > &literals)
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:698
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
SharedBoundsManager * bounds
Collection of objects used to extend the Constraint Solver library.
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1377
const LiteralIndex kNoLiteralIndex(-1)
const Trail & LiteralTrail() const
Definition: sat_solver.h:362
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:44
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1519
int64_t value
Literal literal
Definition: optimization.cc:85
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:383
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()>> heuristics)
LiteralIndex GetDecision(const std::function< BooleanOrIntegerLiteral()> &f)
const int INFO
Definition: log_severity.h:31