OR-Tools  9.6
lb_tree_search.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <functional>
19 #include <memory>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/random/distributions.h"
25 #include "absl/strings/str_cat.h"
26 #include "absl/time/clock.h"
27 #include "absl/time/time.h"
28 #include "ortools/base/logging.h"
31 #include "ortools/sat/integer.h"
35 #include "ortools/sat/model.h"
36 #include "ortools/sat/sat_base.h"
38 #include "ortools/sat/sat_parameters.pb.h"
39 #include "ortools/sat/sat_solver.h"
41 #include "ortools/sat/util.h"
44 
45 namespace operations_research {
46 namespace sat {
47 
49  : time_limit_(model->GetOrCreate<TimeLimit>()),
50  random_(model->GetOrCreate<ModelRandomGenerator>()),
51  sat_solver_(model->GetOrCreate<SatSolver>()),
52  integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
53  trail_(model->GetOrCreate<Trail>()),
54  integer_trail_(model->GetOrCreate<IntegerTrail>()),
55  watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
56  shared_response_(model->GetOrCreate<SharedResponseManager>()),
57  sat_decision_(model->GetOrCreate<SatDecisionPolicy>()),
58  search_helper_(model->GetOrCreate<IntegerSearchHelper>()),
59  parameters_(*model->GetOrCreate<SatParameters>()) {
60  // We should create this class only in the presence of an objective.
61  //
62  // TODO(user): Starts with an initial variable score for all variable in
63  // the objective at their minimum value? this should emulate the first step of
64  // the core approach and gives a similar bound.
65  const ObjectiveDefinition* objective = model->Get<ObjectiveDefinition>();
66  CHECK(objective != nullptr);
67  objective_var_ = objective->objective_var;
68 
69  // Identify an LP with the same objective variable.
70  //
71  // TODO(user): if we have many independent LP, this will find nothing.
74  if (lp->ObjectiveVariable() == objective_var_) {
75  lp_constraint_ = lp;
76  }
77  }
78 
79  // We use the normal SAT search but we will bump the variable activity
80  // slightly differently. In addition to the conflicts, we also bump it each
81  // time the objective lower bound increase in a sub-node.
82  search_heuristic_ =
84  model->GetOrCreate<SearchHeuristics>()->fixed_search});
85 
86  last_logging_time_ = absl::Now();
87 }
88 
89 void LbTreeSearch::UpdateParentObjective(int level) {
90  CHECK_GE(level, 0);
91  CHECK_LT(level, current_branch_.size());
92  if (level == 0) return;
93  const NodeIndex parent_index = current_branch_[level - 1];
94  Node& parent = nodes_[parent_index];
95  const NodeIndex child_index = current_branch_[level];
96  const Node& child = nodes_[child_index];
97  if (parent.true_child == child_index) {
98  parent.UpdateTrueObjective(child.MinObjective());
99  } else {
100  CHECK_EQ(parent.false_child, child_index);
101  parent.UpdateFalseObjective(child.MinObjective());
102  }
103 }
104 
105 void LbTreeSearch::UpdateObjectiveFromParent(int level) {
106  CHECK_GE(level, 0);
107  CHECK_LT(level, current_branch_.size());
108  if (level == 0) return;
109  const NodeIndex parent_index = current_branch_[level - 1];
110  const Node& parent = nodes_[parent_index];
111  CHECK_GE(parent.MinObjective(), current_objective_lb_);
112  const NodeIndex child_index = current_branch_[level];
113  Node& child = nodes_[child_index];
114  if (parent.true_child == child_index) {
115  child.UpdateObjective(parent.true_objective);
116  } else {
117  CHECK_EQ(parent.false_child, child_index);
118  child.UpdateObjective(parent.false_objective);
119  }
120 }
121 
122 void LbTreeSearch::DebugDisplayTree(NodeIndex root) const {
123  int num_nodes = 0;
124  const IntegerValue root_lb = nodes_[root].MinObjective();
125  const auto shifted_lb = [root_lb](IntegerValue lb) {
126  return std::max<int64_t>(0, (lb - root_lb).value());
127  };
128 
129  absl::StrongVector<NodeIndex, int> level(nodes_.size(), 0);
130  std::vector<NodeIndex> to_explore = {root};
131  while (!to_explore.empty()) {
132  NodeIndex n = to_explore.back();
133  to_explore.pop_back();
134 
135  ++num_nodes;
136  const Node& node = nodes_[n];
137 
138  std::string s(level[n], ' ');
139  absl::StrAppend(&s, "#", n.value());
140 
141  if (node.true_child < nodes_.size()) {
142  absl::StrAppend(&s, " [t:#", node.true_child.value(), " ",
143  shifted_lb(node.true_objective), "]");
144  to_explore.push_back(node.true_child);
145  level[node.true_child] = level[n] + 1;
146  } else {
147  absl::StrAppend(&s, " [t:## ", shifted_lb(node.true_objective), "]");
148  }
149  if (node.false_child < nodes_.size()) {
150  absl::StrAppend(&s, " [f:#", node.false_child.value(), " ",
151  shifted_lb(node.false_objective), "]");
152  to_explore.push_back(node.false_child);
153  level[node.false_child] = level[n] + 1;
154  } else {
155  absl::StrAppend(&s, " [f:## ", shifted_lb(node.false_objective), "]");
156  }
157  LOG(INFO) << s;
158  }
159  LOG(INFO) << "num_nodes: " << num_nodes;
160 }
161 
162 // Here we forgot the whole search tree and restart.
163 //
164 // The idea is that the heuristic has now more information so it will likely
165 // take better decision which will result in a smaller overall tree.
166 bool LbTreeSearch::FullRestart() {
167  ++num_full_restarts_;
168  num_decisions_taken_at_last_restart_ = num_decisions_taken_;
169  num_nodes_in_tree_ = 0;
170  nodes_.clear();
171  current_branch_.clear();
172  return sat_solver_->RestoreSolverToAssumptionLevel();
173 }
174 
175 void LbTreeSearch::MarkAsDeletedNodeAndUnreachableSubtree(Node& node) {
176  --num_nodes_in_tree_;
177  node.is_deleted = true;
178  if (sat_solver_->Assignment().LiteralIsTrue(node.literal)) {
179  MarkSubtreeAsDeleted(node.false_child);
180  } else {
181  MarkSubtreeAsDeleted(node.true_child);
182  }
183 }
184 
185 void LbTreeSearch::MarkSubtreeAsDeleted(NodeIndex root) {
186  std::vector<NodeIndex> to_delete{root};
187  for (int i = 0; i < to_delete.size(); ++i) {
188  const NodeIndex n = to_delete[i];
189  if (n >= nodes_.size()) continue;
190 
191  --num_nodes_in_tree_;
192  nodes_[n].is_deleted = true;
193 
194  to_delete.push_back(nodes_[n].true_child);
195  to_delete.push_back(nodes_[n].false_child);
196  }
197 }
198 
199 std::string LbTreeSearch::SmallProgressString() const {
200  return absl::StrCat(
201  "#nodes:", num_nodes_in_tree_, "/", nodes_.size(),
202  " #rc:", num_rc_detected_, " #decisions:", num_decisions_taken_,
203  " #@root:", num_back_to_root_node_, " #restarts:", num_full_restarts_);
204 }
205 
207  const std::function<void()>& feasible_solution_observer) {
208  if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
209  return sat_solver_->UnsatStatus();
210  }
211 
212  // We currently restart the search tree from scratch from time to times:
213  // - Initially, every kNumDecisionsBeforeInitialRestarts, for at most
214  // kMaxNumInitialRestarts times.
215  // - Every time we backtrack to level zero, we count how many nodes are worse
216  // than the best known objective lower bound. If this is true for more than
217  // half of the existing nodes, we restart and clear all nodes. If if this
218  // happens during the initial restarts phase, it reset the above counter and
219  // uses 1 of the available initial restarts.
220  //
221  // This has 2 advantages:
222  // - It allows our "pseudo-cost" to kick in and experimentally result in
223  // smaller trees down the road.
224  // - It removes large inefficient search trees.
225  //
226  // TODO(user): a strong branching initial start, or allowing a few decision
227  // per nodes might be a better approach.
228  //
229  // TODO(user): It would also be cool to exploit the reason for the LB increase
230  // even more.
231  const int kMaxNumInitialRestarts = 10;
232  const int64_t kNumDecisionsBeforeInitialRestarts = 1000;
233 
234  while (!time_limit_->LimitReached() && !shared_response_->ProblemIsSolved()) {
235  // This is the current bound we try to improve. We cache it here to avoid
236  // getting the lock many times and it is also easier to follow the code if
237  // this is assumed constant for one iteration.
238  current_objective_lb_ = shared_response_->GetInnerObjectiveLowerBound();
239 
240  // If some branches already have a good lower bound, no need to call the LP
241  // on those.
242  watcher_->SetStopPropagationCallback([this] {
243  return integer_trail_->LowerBound(objective_var_) > current_objective_lb_;
244  });
245 
246  // Propagate upward in the tree the new objective lb.
247  if (!current_branch_.empty()) {
248  // Our branch is always greater or equal to the level.
249  // We increase the objective_lb of the current node if needed.
250  {
251  const int current_level = sat_solver_->CurrentDecisionLevel();
252  CHECK_GE(current_branch_.size(), current_level);
253  for (int i = 0; i < current_level; ++i) {
254  CHECK(sat_solver_->Assignment().LiteralIsAssigned(
255  nodes_[current_branch_[i]].literal));
256  }
257  if (current_level < current_branch_.size()) {
258  nodes_[current_branch_[current_level]].UpdateObjective(
259  integer_trail_->LowerBound(objective_var_));
260  }
261 
262  // Minor optim: sometimes, because of the LP and cuts, the reason for
263  // objective_var_ only contains lower level literals, so we can exploit
264  // that.
265  //
266  // TODO(user): No point checking that if the objective lb wasn't
267  // assigned at this level.
268  //
269  // TODO(user): Exploit the reasons further.
270  if (integer_trail_->LowerBound(objective_var_) >
271  integer_trail_->LevelZeroLowerBound(objective_var_)) {
272  const std::vector<Literal> reason =
274  objective_var_, integer_trail_->LowerBound(objective_var_)));
275  int max_level = 0;
276  for (const Literal l : reason) {
277  max_level = std::max<int>(
278  max_level,
279  sat_solver_->LiteralTrail().Info(l.Variable()).level);
280  }
281  if (max_level < current_level) {
282  nodes_[current_branch_[max_level]].UpdateObjective(
283  integer_trail_->LowerBound(objective_var_));
284  }
285  }
286  }
287 
288  // Propagate upward and then forward any new bounds.
289  for (int level = current_branch_.size(); --level > 0;) {
290  UpdateParentObjective(level);
291  }
292  nodes_[current_branch_[0]].UpdateObjective(current_objective_lb_);
293  for (int level = 1; level < current_branch_.size(); ++level) {
294  UpdateObjectiveFromParent(level);
295  }
296 
297  // If the root lb increased, update global shared objective lb.
298  const IntegerValue bound = nodes_[current_branch_[0]].MinObjective();
299  if (bound > current_objective_lb_) {
300  shared_response_->UpdateInnerObjectiveBounds(
301  absl::StrCat("lb_tree_search ", SmallProgressString()), bound,
302  integer_trail_->LevelZeroUpperBound(objective_var_));
303  current_objective_lb_ = bound;
304  if (VLOG_IS_ON(3)) DebugDisplayTree(current_branch_[0]);
305  }
306  }
307 
308  // Each time we are back here, we bump the activities of the variable that
309  // are part of the objective lower bound reason.
310  //
311  // Note that this is why we prefer not to increase the lower zero lower
312  // bound of objective_var_ with the tree root lower bound, so we can exploit
313  // more reasons.
314  //
315  // TODO(user): This is slightly different than bumping each time we
316  // push a decision that result in an LB increase. This is also called on
317  // backjump for instance.
318  if (integer_trail_->LowerBound(objective_var_) >
319  integer_trail_->LevelZeroLowerBound(objective_var_)) {
320  std::vector<Literal> reason =
322  objective_var_, integer_trail_->LowerBound(objective_var_)));
323  sat_decision_->BumpVariableActivities(reason);
324  sat_decision_->UpdateVariableActivityIncrement();
325  }
326 
327  // Forget the whole tree and restart.
328  // We will do it periodically at the beginning of the search each time we
329  // cross the kNumDecisionsBeforeInitialRestarts decision since the last
330  // restart. This will happen at most kMaxNumInitialRestarts times.
331  if (num_decisions_taken_ >= num_decisions_taken_at_last_restart_ +
332  kNumDecisionsBeforeInitialRestarts &&
333  num_full_restarts_ < kMaxNumInitialRestarts) {
334  VLOG(2) << "lb_tree_search initial_restart " << SmallProgressString();
335  if (!FullRestart()) return sat_solver_->UnsatStatus();
336  }
337 
338  // Backtrack if needed.
339  //
340  // Our algorithm stop exploring a branch as soon as its objective lower
341  // bound is greater than the root lower bound. We then backtrack to the
342  // first node in the branch that is not yet closed under this bound.
343  //
344  // TODO(user): If we remember how far we can backjump for both true/false
345  // branch, we could be more efficient.
346  while (current_branch_.size() > sat_solver_->CurrentDecisionLevel() + 1 ||
347  (current_branch_.size() > 1 &&
348  nodes_[current_branch_.back()].MinObjective() >
349  current_objective_lb_)) {
350  current_branch_.pop_back();
351  }
352 
353  // Backtrack the solver.
354  {
355  int backtrack_level =
356  std::max(0, static_cast<int>(current_branch_.size()) - 1);
357 
358  // Periodic backtrack to level zero so we can import bounds.
359  if (num_decisions_taken_ >=
360  num_decisions_taken_at_last_level_zero_ + 10000) {
361  backtrack_level = 0;
362  }
363 
364  sat_solver_->Backtrack(backtrack_level);
365  if (!sat_solver_->FinishPropagation()) {
366  return sat_solver_->UnsatStatus();
367  }
368  }
369 
370  if (sat_solver_->CurrentDecisionLevel() == 0) {
371  ++num_back_to_root_node_;
372  num_decisions_taken_at_last_level_zero_ = num_decisions_taken_;
373  }
374 
375  // This will import other workers bound if we are back to level zero.
376  if (!search_helper_->BeforeTakingDecision()) {
377  return sat_solver_->UnsatStatus();
378  }
379 
380  // If the search has not just been restarted (in which case nodes_ would be
381  // empty), and if we are at level zero (either naturally, or if the
382  // backtrack level was set to zero in the above code), let's run a different
383  // heuristic to decide whether to restart the search from scratch or not.
384  //
385  // We ignore small search trees.
386  if (sat_solver_->CurrentDecisionLevel() == 0 && num_nodes_in_tree_ > 50) {
387  // Let's count how many nodes have worse objective bounds than the best
388  // known external objective lower bound.
389  const IntegerValue latest_lb =
390  shared_response_->GetInnerObjectiveLowerBound();
391  int num_nodes = 0;
392  int num_nodes_with_lower_objective = 0;
393  for (const Node& node : nodes_) {
394  if (node.is_deleted) continue;
395  ++num_nodes;
396  if (node.MinObjective() < latest_lb) num_nodes_with_lower_objective++;
397  }
398  DCHECK_EQ(num_nodes_in_tree_, num_nodes);
399  if (num_nodes_with_lower_objective * 2 > num_nodes) {
400  VLOG(2) << "lb_tree_search restart nodes: "
401  << num_nodes_with_lower_objective << "/" << num_nodes << " : "
402  << 100.0 * num_nodes_with_lower_objective / num_nodes << "%"
403  << ", decisions:" << num_decisions_taken_;
404  if (!FullRestart()) return sat_solver_->UnsatStatus();
405  }
406  }
407 
408  // Dive: Follow the branch with lowest objective.
409  // Note that we do not creates new nodes here.
410  //
411  // TODO(user): If we have new information and our current objective bound
412  // is higher than any bound in a whole subtree, we might want to just
413  // restart this subtree exploration?
414  while (current_branch_.size() == sat_solver_->CurrentDecisionLevel() + 1) {
415  const int level = current_branch_.size() - 1;
416  CHECK_EQ(level, sat_solver_->CurrentDecisionLevel());
417  Node& node = nodes_[current_branch_[level]];
418  node.UpdateObjective(std::max(
419  current_objective_lb_, integer_trail_->LowerBound(objective_var_)));
420  if (node.MinObjective() > current_objective_lb_) break;
421  CHECK_EQ(node.MinObjective(), current_objective_lb_) << level;
422 
423  // This will be set to the next node index.
424  NodeIndex n;
425 
426  // If the variable is already fixed, we bypass the node and connect
427  // its parent directly to the relevant child.
428  if (sat_solver_->Assignment().LiteralIsAssigned(node.literal)) {
429  IntegerValue new_lb;
430  if (sat_solver_->Assignment().LiteralIsTrue(node.literal)) {
431  n = node.true_child;
432  new_lb = node.true_objective;
433  } else {
434  n = node.false_child;
435  new_lb = node.false_objective;
436  }
437  MarkAsDeletedNodeAndUnreachableSubtree(node);
438 
439  // We jump directly to the subnode.
440  // Else we will change the root.
441  current_branch_.pop_back();
442  if (!current_branch_.empty()) {
443  const NodeIndex parent = current_branch_.back();
444  if (sat_solver_->Assignment().LiteralIsTrue(nodes_[parent].literal)) {
445  nodes_[parent].true_child = n;
446  nodes_[parent].UpdateTrueObjective(new_lb);
447  } else {
448  DCHECK(sat_solver_->Assignment().LiteralIsFalse(
449  nodes_[parent].literal));
450  nodes_[parent].false_child = n;
451  nodes_[parent].UpdateFalseObjective(new_lb);
452  }
453  if (nodes_[parent].MinObjective() > current_objective_lb_) break;
454  }
455  } else {
456  // See if we have better bounds using the current LP state.
457  ExploitReducedCosts(current_branch_[level]);
458 
459  // If both lower bound are the same, we pick the literal branch. We do
460  // that because this is the polarity that was chosen by the SAT
461  // heuristic in the first place. We tried random, it doesn't seems to
462  // work as well.
463  num_decisions_taken_++;
464  const bool choose_true = node.true_objective <= node.false_objective;
465  if (choose_true) {
466  n = node.true_child;
467  search_helper_->TakeDecision(node.literal);
468  } else {
469  n = node.false_child;
470  search_helper_->TakeDecision(node.literal.Negated());
471  }
472 
473  // Conflict?
474  if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
475  if (choose_true) {
476  node.UpdateTrueObjective(kMaxIntegerValue);
477  } else {
478  node.UpdateFalseObjective(kMaxIntegerValue);
479  }
480  break;
481  }
482 
483  // Update the proper field and abort the dive if we crossed the
484  // threshold.
485  const IntegerValue lb = integer_trail_->LowerBound(objective_var_);
486  if (choose_true) {
487  node.UpdateTrueObjective(lb);
488  } else {
489  node.UpdateFalseObjective(lb);
490  }
491  if (lb > current_objective_lb_) break;
492  }
493 
494  shared_response_->LogPeriodicMessage(
495  "TreeS", SmallProgressString(),
496  parameters_.log_frequency_in_seconds(), &last_logging_time_);
497 
498  if (n < nodes_.size()) {
499  current_branch_.push_back(n);
500  } else {
501  break;
502  }
503  }
504 
505  // If a conflict occurred, we will backtrack.
506  if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
507  continue;
508  }
509 
510  // This test allow to not take a decision when the branch is already closed
511  // (i.e. the true branch or false branch lb is high enough). Adding it
512  // basically changes if we take the decision later when we explore the
513  // branch or right now.
514  //
515  // I feel taking it later is better. It also avoid creating uneeded nodes.
516  // It does change the behavior on a few problem though. For instance on
517  // irp.mps.gz, the search works better without this, whatever the random
518  // seed. Not sure why, maybe it creates more diversity?
519  //
520  // Another difference is that if the search is done and we have a feasible
521  // solution, we will not report it because of this test (except if we are
522  // at the optimal).
523  if (integer_trail_->LowerBound(objective_var_) > current_objective_lb_) {
524  continue;
525  }
526 
527  // We are about to take a new decision, what we will do is dive until
528  // the objective lower bound increase. we will then create a bunch of new
529  // nodes in the tree.
530  //
531  // By analyzing the reason for the increase, we can create less nodes than
532  // if we just followed the initial heuristic.
533  //
534  // TODO(user): In multithread, this change the behavior a lot since we
535  // dive until we beat the best shared bound. Maybe we shouldn't do that.
536  const int base_level = sat_solver_->CurrentDecisionLevel();
537  while (true) {
538  // TODO(user): We sometimes branch on the objective variable, this should
539  // probably be avoided.
540  const LiteralIndex decision =
541  search_helper_->GetDecision(search_heuristic_);
542 
543  // No new decision: search done.
544  if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
545  if (decision == kNoLiteralIndex) {
546  feasible_solution_observer();
547  break;
548  }
549 
550  num_decisions_taken_++;
551  if (!search_helper_->TakeDecision(Literal(decision))) {
552  return sat_solver_->UnsatStatus();
553  }
554  if (sat_solver_->CurrentDecisionLevel() < base_level) break;
555  if (integer_trail_->LowerBound(objective_var_) > current_objective_lb_) {
556  break;
557  }
558  }
559  if (sat_solver_->CurrentDecisionLevel() <= base_level) continue;
560 
561  // Analyse the reason for objective increase. Deduce a set of new nodes to
562  // append to the tree.
563  //
564  // TODO(user): Try to minimize the number of decisions?
565  const std::vector<Literal> reason =
567  objective_var_, integer_trail_->LowerBound(objective_var_)));
568  std::vector<Literal> decisions = ExtractDecisions(base_level, reason);
569 
570  // Bump activities.
571  sat_decision_->BumpVariableActivities(reason);
572  sat_decision_->BumpVariableActivities(decisions);
573  sat_decision_->UpdateVariableActivityIncrement();
574 
575  // Create one node per new decisions.
576  CHECK_EQ(current_branch_.size(), base_level);
577  for (const Literal d : decisions) {
578  AppendNewNodeToCurrentBranch(d);
579  }
580 
581  // Update the objective of the last node in the branch since we just
582  // improved that.
583  if (!current_branch_.empty()) {
584  Node& n = nodes_[current_branch_.back()];
585  if (sat_solver_->Assignment().LiteralIsTrue(n.literal)) {
586  n.UpdateTrueObjective(integer_trail_->LowerBound(objective_var_));
587  } else {
588  n.UpdateFalseObjective(integer_trail_->LowerBound(objective_var_));
589  }
590  }
591 
592  // Reset the solver to a correct state since we have a subset of the
593  // current propagation. We backtrack as little as possible.
594  //
595  // The decision level is the number of decision taken.
596  // Decision()[level] is the decision at that level.
597  int backtrack_level = base_level;
598  CHECK_LE(current_branch_.size(), sat_solver_->CurrentDecisionLevel());
599  while (backtrack_level < current_branch_.size() &&
600  sat_solver_->Decisions()[backtrack_level].literal ==
601  nodes_[current_branch_[backtrack_level]].literal) {
602  ++backtrack_level;
603  }
604  sat_solver_->Backtrack(backtrack_level);
605 
606  // Update bounds with reduced costs info.
607  //
608  // TODO(user): Uses old optimal constraint that we just potentially
609  // backtracked over?
610  //
611  // TODO(user): We could do all at once rather than in O(#decision * #size).
612  for (int i = backtrack_level; i < current_branch_.size(); ++i) {
613  ExploitReducedCosts(current_branch_[i]);
614  }
615  }
616 
618 }
619 
620 std::vector<Literal> LbTreeSearch::ExtractDecisions(
621  int base_level, const std::vector<Literal>& conflict) {
622  std::vector<int> num_per_level(sat_solver_->CurrentDecisionLevel() + 1, 0);
623  std::vector<bool> is_marked;
624  for (const Literal l : conflict) {
625  const AssignmentInfo& info = trail_->Info(l.Variable());
626  if (info.level <= base_level) continue;
627  num_per_level[info.level]++;
628  if (info.trail_index >= is_marked.size()) {
629  is_marked.resize(info.trail_index + 1);
630  }
631  is_marked[info.trail_index] = true;
632  }
633 
634  std::vector<Literal> result;
635  if (is_marked.empty()) return result;
636  for (int i = is_marked.size() - 1; i >= 0; --i) {
637  if (!is_marked[i]) continue;
638 
639  const Literal l = (*trail_)[i];
640  const AssignmentInfo& info = trail_->Info(l.Variable());
641  if (info.level <= base_level) break;
642  if (num_per_level[info.level] == 1) {
643  result.push_back(l);
644  continue;
645  }
646 
647  // Expand.
648  num_per_level[info.level]--;
649  for (const Literal new_l : trail_->Reason(l.Variable())) {
650  const AssignmentInfo& new_info = trail_->Info(new_l.Variable());
651  if (new_info.level <= base_level) continue;
652  if (is_marked[new_info.trail_index]) continue;
653  is_marked[new_info.trail_index] = true;
654  num_per_level[new_info.level]++;
655  }
656  }
657 
658  // We prefer to keep the same order.
659  std::reverse(result.begin(), result.end());
660  return result;
661 }
662 
663 void LbTreeSearch::AppendNewNodeToCurrentBranch(Literal decision) {
664  const NodeIndex n(nodes_.size());
665  ++num_nodes_in_tree_;
666  nodes_.emplace_back(Literal(decision), current_objective_lb_);
667  if (!current_branch_.empty()) {
668  const NodeIndex parent = current_branch_.back();
669  if (sat_solver_->Assignment().LiteralIsTrue(nodes_[parent].literal)) {
670  nodes_[parent].true_child = n;
671  nodes_[parent].UpdateTrueObjective(nodes_.back().MinObjective());
672  } else {
673  CHECK(sat_solver_->Assignment().LiteralIsFalse(nodes_[parent].literal));
674  nodes_[parent].false_child = n;
675  nodes_[parent].UpdateFalseObjective(nodes_.back().MinObjective());
676  }
677  }
678  current_branch_.push_back(n);
679 }
680 
681 // Looking at the reduced costs, we can already have a bound for one of the
682 // branch. Increasing the corresponding objective can save some branches,
683 // and also allow for a more incremental LP solving since we do less back
684 // and forth.
685 //
686 // TODO(user): The code to recover that is a bit convoluted. Alternatively
687 // Maybe we should do a "fast" propagation without the LP in each branch.
688 // That will work as long as we keep these optimal LP constraints around
689 // and propagate them.
690 //
691 // TODO(user): Incorporate this in the heuristic so we choose more Booleans
692 // inside these LP explanations?
693 void LbTreeSearch::ExploitReducedCosts(NodeIndex n) {
694  if (lp_constraint_ == nullptr) return;
695 
696  // TODO(user): we could consider earlier constraints instead of just
697  // looking at the last one, but experiments didn't really show a big
698  // gain.
699  const auto& cts = lp_constraint_->OptimalConstraints();
700  if (cts.empty()) return;
701  const std::unique_ptr<IntegerSumLE>& rc = cts.back();
702 
703  // Note that this return literal EQUIVALENT to the node.literal, not just
704  // implied by it. We need that for correctness.
705  int num_tests = 0;
706  Node& node = nodes_[n];
707  CHECK(!sat_solver_->Assignment().LiteralIsAssigned(node.literal));
708  for (const IntegerLiteral integer_literal :
709  integer_encoder_->GetIntegerLiterals(node.literal)) {
710  if (integer_trail_->IsCurrentlyIgnored(integer_literal.var)) continue;
711 
712  // To avoid bad corner case. Not sure it ever triggers.
713  if (++num_tests > 10) break;
714 
715  const std::pair<IntegerValue, IntegerValue> bounds =
716  rc->ConditionalLb(integer_literal, objective_var_);
717  if (bounds.first > node.false_objective) {
718  ++num_rc_detected_;
719  node.UpdateFalseObjective(bounds.first);
720  }
721  if (bounds.second > node.true_objective) {
722  ++num_rc_detected_;
723  node.UpdateTrueObjective(bounds.second);
724  }
725  }
726 }
727 
728 } // namespace sat
729 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
size_type size() const
void push_back(const value_type &x)
void emplace_back(Args &&... args)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:552
void SetStopPropagationCallback(std::function< bool()> callback)
Definition: integer.h:1430
const InlinedIntegerLiteralVector & GetIntegerLiterals(Literal lit) const
Definition: integer.h:524
LiteralIndex GetDecision(const std::function< BooleanOrIntegerLiteral()> &f)
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:775
std::vector< Literal > ReasonFor(IntegerLiteral literal) const
Definition: integer.cc:1873
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1646
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1641
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
const std::vector< std::unique_ptr< IntegerSumLE > > & OptimalConstraints() const
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void BumpVariableActivities(const std::vector< Literal > &literals)
const Trail & LiteralTrail() const
Definition: sat_solver.h:387
const VariablesAssignment & Assignment() const
Definition: sat_solver.h:388
void Backtrack(int target_level)
Definition: sat_solver.cc:1004
const std::vector< Decision > & Decisions() const
Definition: sat_solver.h:385
void LogPeriodicMessage(const std::string &prefix, const std::string &message, double frequency_seconds, absl::Time *last_logging_time)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:403
absl::Span< const Literal > Reason(BooleanVariable var) const
Definition: sat_base.h:617
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:167
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:164
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:161
SharedBoundsManager * bounds
int64_t value
GRBmodel * model
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()>> heuristics)
Collection of objects used to extend the Constraint Solver library.
int64_t bound
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499
#define VLOG(verboselevel)
Definition: vlog.h:39
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:47