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 
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_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 
48 bool 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  {}, {})) {
190  return SatSolver::INFEASIBLE;
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
#define CHECK(condition)
Definition: base/logging.h:491
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
int64_t min
Definition: alldiff_cst.cc:139
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1345
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:148
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:151
GRBmodel * model
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::vector< Literal > ReasonFor(IntegerLiteral literal) const
Definition: integer.cc:1617
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::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 LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:154
void Backtrack(int target_level)
Definition: sat_solver.cc:889
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
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:533
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
void BumpVariableActivities(const std::vector< Literal > &literals)
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1349
Collection of objects used to extend the Constraint Solver library.
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1309
const LiteralIndex kNoLiteralIndex(-1)
const Trail & LiteralTrail() const
Definition: sat_solver.h:362
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1407
Literal literal
Definition: optimization.cc:85
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:382
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()>> heuristics)
LiteralIndex GetDecision(const std::function< BooleanOrIntegerLiteral()> &f)