Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
lb_tree_search.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <limits>
20#include <memory>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/cleanup/cleanup.h"
26#include "absl/log/check.h"
27#include "absl/strings/str_cat.h"
28#include "absl/types/span.h"
32#include "ortools/sat/integer.h"
36#include "ortools/sat/model.h"
40#include "ortools/sat/sat_parameters.pb.h"
43#include "ortools/sat/util.h"
46
47namespace operations_research {
48namespace sat {
49
51 : name_(model->Name()),
52 time_limit_(model->GetOrCreate<TimeLimit>()),
53 random_(model->GetOrCreate<ModelRandomGenerator>()),
54 sat_solver_(model->GetOrCreate<SatSolver>()),
55 integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
56 trail_(model->GetOrCreate<Trail>()),
57 assignment_(trail_->Assignment()),
58 integer_trail_(model->GetOrCreate<IntegerTrail>()),
59 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
60 shared_response_(model->GetOrCreate<SharedResponseManager>()),
61 pseudo_costs_(model->GetOrCreate<PseudoCosts>()),
62 sat_decision_(model->GetOrCreate<SatDecisionPolicy>()),
63 search_helper_(model->GetOrCreate<IntegerSearchHelper>()),
64 parameters_(*model->GetOrCreate<SatParameters>()) {
65 // We should create this class only in the presence of an objective.
66 //
67 // TODO(user): Starts with an initial variable score for all variable in
68 // the objective at their minimum value? this should emulate the first step of
69 // the core approach and gives a similar bound.
70 const ObjectiveDefinition* objective = model->Get<ObjectiveDefinition>();
71 DCHECK(objective != nullptr);
72 objective_var_ = objective->objective_var;
73
74 // Identify an LP with the same objective variable.
75 //
76 // TODO(user): if we have many independent LP, this will find nothing.
79 if (lp->ObjectiveVariable() == objective_var_) {
80 lp_constraint_ = lp;
81 }
82 }
83
84 // We use the normal SAT search but we will bump the variable activity
85 // slightly differently. In addition to the conflicts, we also bump it each
86 // time the objective lower bound increase in a sub-node.
87 std::vector<std::function<BooleanOrIntegerLiteral()>> heuristics;
88 if (SaveLpBasisOption()) {
89 heuristics.emplace_back(LpPseudoCostHeuristic(model));
90 }
91 heuristics.emplace_back(SatSolverHeuristic(model));
92 heuristics.emplace_back(MostFractionalHeuristic(model));
93 heuristics.emplace_back(IntegerValueSelectionHeuristic(
94 model->GetOrCreate<SearchHeuristics>()->fixed_search, model));
95 search_heuristic_ = SequentialSearch(std::move(heuristics));
96}
97
98bool LbTreeSearch::NodeHasBasis(const Node& node) const {
99 return !node.basis.IsEmpty();
100}
101
102bool LbTreeSearch::NodeHasUpToDateBasis(const Node& node) const {
103 if (node.basis.IsEmpty()) return false;
104
105 // TODO(user): Do something smarter. We can at least reuse the variable
106 // statuses maybe?
107 if (node.basis_timestamp != lp_constraint_->num_lp_changes()) {
108 return false;
109 }
110 return true;
111}
112
113void LbTreeSearch::EnableLpAndLoadBestBasis() {
114 DCHECK(lp_constraint_ != nullptr);
115 lp_constraint_->EnablePropagation(true);
116
117 const int level = sat_solver_->CurrentDecisionLevel();
118 if (current_branch_.empty()) return;
119
120 NodeIndex n = current_branch_[0]; // Root.
121 int basis_level = -1;
122 NodeIndex last_node_with_basis(-1);
123 for (int i = 0; i < level; ++i) {
124 if (n >= nodes_.size()) break;
125 if (NodeHasBasis(nodes_[n])) {
126 basis_level = i;
127 last_node_with_basis = n;
128 }
129 const Literal decision = sat_solver_->Decisions()[i].literal;
130 if (nodes_[n].literal_index == decision.Index()) {
131 n = nodes_[n].true_child;
132 } else {
133 DCHECK_EQ(nodes_[n].literal_index, decision.NegatedIndex());
134 n = nodes_[n].false_child;
135 }
136 }
137 if (n < nodes_.size()) {
138 if (NodeHasBasis(nodes_[n])) {
139 basis_level = level;
140 last_node_with_basis = n;
141 }
142 }
143
144 if (last_node_with_basis == -1) {
145 VLOG(1) << "no basis?";
146 return;
147 }
148 VLOG(1) << "load " << basis_level << " / " << level;
149
150 if (!NodeHasUpToDateBasis(nodes_[last_node_with_basis])) {
151 // The basis is no longer up to date, for now we do not load it.
152 // TODO(user): try to do something about it.
153 VLOG(1) << "Skipping potentially bad basis.";
154 return;
155 }
156
157 lp_constraint_->LoadBasisState(nodes_[last_node_with_basis].basis);
158}
159
160void LbTreeSearch::SaveLpBasisInto(Node& node) {
161 node.basis_timestamp = lp_constraint_->num_lp_changes();
162 node.basis = lp_constraint_->GetBasisState();
163}
164
165void LbTreeSearch::UpdateParentObjective(int level) {
166 DCHECK_GE(level, 0);
167 DCHECK_LT(level, current_branch_.size());
168 if (level == 0) return;
169 const NodeIndex parent_index = current_branch_[level - 1];
170 Node& parent = nodes_[parent_index];
171 const NodeIndex child_index = current_branch_[level];
172 const Node& child = nodes_[child_index];
173 if (parent.true_child == child_index) {
174 parent.UpdateTrueObjective(child.MinObjective());
175 } else {
176 DCHECK_EQ(parent.false_child, child_index);
177 parent.UpdateFalseObjective(child.MinObjective());
178 }
179}
180
181void LbTreeSearch::UpdateObjectiveFromParent(int level) {
182 DCHECK_GE(level, 0);
183 DCHECK_LT(level, current_branch_.size());
184 if (level == 0) return;
185 const NodeIndex parent_index = current_branch_[level - 1];
186 const Node& parent = nodes_[parent_index];
187 DCHECK_GE(parent.MinObjective(), current_objective_lb_);
188 const NodeIndex child_index = current_branch_[level];
189 Node& child = nodes_[child_index];
190 if (parent.true_child == child_index) {
191 child.UpdateObjective(parent.true_objective);
192 } else {
193 DCHECK_EQ(parent.false_child, child_index);
194 child.UpdateObjective(parent.false_objective);
195 }
196}
197
198std::string LbTreeSearch::NodeDebugString(NodeIndex n) const {
199 const IntegerValue root_lb = current_objective_lb_;
200 const auto shifted_lb = [root_lb](IntegerValue lb) {
201 return std::max<int64_t>(0, (lb - root_lb).value());
202 };
203
204 std::string s;
205 absl::StrAppend(&s, "#", n.value());
206
207 const Node& node = nodes_[n];
208 std::string true_letter = "t";
209 std::string false_letter = "f";
210 if (node.literal_index != kNoLiteralIndex && !node.is_deleted) {
211 const Literal decision = node.Decision();
212 if (assignment_.LiteralIsTrue(decision)) {
213 true_letter = "T";
214 }
215 if (assignment_.LiteralIsFalse(decision)) {
216 false_letter = "F";
217 }
218 }
219
220 if (node.true_child < nodes_.size()) {
221 absl::StrAppend(&s, " [", true_letter, ":#", node.true_child.value(), " ",
222 shifted_lb(node.true_objective), "]");
223 } else {
224 absl::StrAppend(&s, " [", true_letter, ":## ",
225 shifted_lb(node.true_objective), "]");
226 }
227 if (node.false_child < nodes_.size()) {
228 absl::StrAppend(&s, " [", false_letter, ":#", node.false_child.value(), " ",
229 shifted_lb(node.false_objective), "]");
230 } else {
231 absl::StrAppend(&s, " [", false_letter, ":## ",
232 shifted_lb(node.false_objective), "]");
233 }
234
235 if (node.is_deleted) {
236 absl::StrAppend(&s, " <D>");
237 }
238 if (NodeHasBasis(node)) {
239 absl::StrAppend(&s, " <B>");
240 }
241
242 return s;
243}
244
245void LbTreeSearch::DebugDisplayTree(NodeIndex root) const {
246 int num_nodes = 0;
247
248 util_intops::StrongVector<NodeIndex, int> level(nodes_.size(), 0);
249 std::vector<NodeIndex> to_explore = {root};
250 while (!to_explore.empty()) {
251 NodeIndex n = to_explore.back();
252 to_explore.pop_back();
253
254 ++num_nodes;
255 const Node& node = nodes_[n];
256
257 std::string s(level[n], ' ');
258 absl::StrAppend(&s, NodeDebugString(n));
259 LOG(INFO) << s;
260
261 if (node.true_child < nodes_.size()) {
262 to_explore.push_back(node.true_child);
263 level[node.true_child] = level[n] + 1;
264 }
265 if (node.false_child < nodes_.size()) {
266 to_explore.push_back(node.false_child);
267 level[node.false_child] = level[n] + 1;
268 }
269 }
270 LOG(INFO) << "num_nodes: " << num_nodes;
271}
272
273// Here we forgot the whole search tree and restart.
274//
275// The idea is that the heuristic has now more information so it will likely
276// take better decision which will result in a smaller overall tree.
277bool LbTreeSearch::FullRestart() {
278 ++num_full_restarts_;
279 num_decisions_taken_at_last_restart_ = num_decisions_taken_;
280 num_nodes_in_tree_ = 0;
281 nodes_.clear();
282 current_branch_.clear();
283 return sat_solver_->RestoreSolverToAssumptionLevel();
284}
285
286void LbTreeSearch::MarkAsDeletedNodeAndUnreachableSubtree(Node& node) {
287 --num_nodes_in_tree_;
288 DCHECK(!node.is_deleted);
289 node.is_deleted = true;
290 DCHECK_NE(node.literal_index, kNoLiteralIndex);
291 if (assignment_.LiteralIsTrue(Literal(node.literal_index))) {
292 MarkSubtreeAsDeleted(node.false_child);
293 } else {
294 MarkSubtreeAsDeleted(node.true_child);
295 }
296}
297
298void LbTreeSearch::MarkBranchAsInfeasible(Node& node, bool true_branch) {
299 if (true_branch) {
300 node.UpdateTrueObjective(kMaxIntegerValue);
301 MarkSubtreeAsDeleted(node.true_child);
302 node.true_child = NodeIndex(std::numeric_limits<int32_t>::max());
303 } else {
304 node.UpdateFalseObjective(kMaxIntegerValue);
305 MarkSubtreeAsDeleted(node.false_child);
306 node.false_child = NodeIndex(std::numeric_limits<int32_t>::max());
307 }
308}
309
310void LbTreeSearch::MarkSubtreeAsDeleted(NodeIndex root) {
311 std::vector<NodeIndex> to_delete{root};
312 for (int i = 0; i < to_delete.size(); ++i) {
313 const NodeIndex n = to_delete[i];
314 if (n >= nodes_.size()) continue;
315 if (nodes_[n].is_deleted) continue;
316
317 --num_nodes_in_tree_;
318 nodes_[n].is_deleted = true;
319
320 to_delete.push_back(nodes_[n].true_child);
321 to_delete.push_back(nodes_[n].false_child);
322 }
323}
324
325std::string LbTreeSearch::SmallProgressString() const {
326 return absl::StrCat(
327 "nodes=", num_nodes_in_tree_, "/", nodes_.size(),
328 " rc=", num_rc_detected_, " decisions=", num_decisions_taken_,
329 " @root=", num_back_to_root_node_, " restarts=", num_full_restarts_,
330 " lp_iters=[", FormatCounter(num_lp_iters_at_level_zero_), ", ",
331 FormatCounter(num_lp_iters_save_basis_), ", ",
332 FormatCounter(num_lp_iters_first_branch_), ", ",
333 FormatCounter(num_lp_iters_dive_), "]");
334}
335
336std::function<void()> LbTreeSearch::UpdateLpIters(int64_t* counter) {
337 if (lp_constraint_ == nullptr) return []() {};
338 const int64_t old_num = lp_constraint_->total_num_simplex_iterations();
339 return [old_num, counter, this]() {
340 const int64_t new_num = lp_constraint_->total_num_simplex_iterations();
341 *counter += new_num - old_num;
342 };
343}
344
345bool LbTreeSearch::LevelZeroLogic() {
346 ++num_back_to_root_node_;
347 num_decisions_taken_at_last_level_zero_ = num_decisions_taken_;
348
349 // Always run the LP when we are back at level zero.
350 if (SaveLpBasisOption() && !current_branch_.empty()) {
351 const auto cleanup =
352 absl::MakeCleanup(UpdateLpIters(&num_lp_iters_at_level_zero_));
353 EnableLpAndLoadBestBasis();
354 if (!sat_solver_->FinishPropagation()) {
355 return false;
356 }
357 SaveLpBasisInto(nodes_[current_branch_[0]]);
358 lp_constraint_->EnablePropagation(false);
359 }
360
361 // Import the objective upper-bound.
362 // We do that manually because we disabled objective import to not "pollute"
363 // the objective lower_bound and still have local reason for objective
364 // improvement.
365 {
366 const IntegerValue ub = shared_response_->GetInnerObjectiveUpperBound();
367 if (integer_trail_->UpperBound(objective_var_) > ub) {
368 if (!integer_trail_->Enqueue(
369 IntegerLiteral::LowerOrEqual(objective_var_, ub), {}, {})) {
370 sat_solver_->NotifyThatModelIsUnsat();
371 return false;
372 }
373 if (!sat_solver_->FinishPropagation()) {
374 return false;
375 }
376 }
377 }
378
379 // If the search has not just been restarted (in which case nodes_ would be
380 // empty), and if we are at level zero (either naturally, or if the
381 // backtrack level was set to zero in the above code), let's run a different
382 // heuristic to decide whether to restart the search from scratch or not.
383 //
384 // We ignore small search trees.
385 if (num_nodes_in_tree_ > 50) {
386 // Let's count how many nodes have worse objective bounds than the best
387 // known external objective lower bound.
388 const IntegerValue latest_lb =
389 shared_response_->GetInnerObjectiveLowerBound();
390 int num_nodes = 0;
391 int num_nodes_with_lower_objective = 0;
392 for (const Node& node : nodes_) {
393 if (node.is_deleted) continue;
394 ++num_nodes;
395 if (node.MinObjective() < latest_lb) num_nodes_with_lower_objective++;
396 }
397 DCHECK_EQ(num_nodes_in_tree_, num_nodes);
398 if (num_nodes_with_lower_objective * 2 > num_nodes) {
399 VLOG(2) << "lb_tree_search restart nodes: "
400 << num_nodes_with_lower_objective << "/" << num_nodes << " : "
401 << 100.0 * num_nodes_with_lower_objective / num_nodes << "%"
402 << ", decisions:" << num_decisions_taken_;
403 if (!FullRestart()) return false;
404 }
405 }
406
407 return true;
408}
409
411 const std::function<void()>& feasible_solution_observer) {
412 if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
413 return sat_solver_->UnsatStatus();
414 }
415
416 // We currently restart the search tree from scratch from time to times:
417 // - Initially, every kNumDecisionsBeforeInitialRestarts, for at most
418 // kMaxNumInitialRestarts times.
419 // - Every time we backtrack to level zero, we count how many nodes are worse
420 // than the best known objective lower bound. If this is true for more than
421 // half of the existing nodes, we restart and clear all nodes. If if this
422 // happens during the initial restarts phase, it reset the above counter and
423 // uses 1 of the available initial restarts.
424 //
425 // This has 2 advantages:
426 // - It allows our "pseudo-cost" to kick in and experimentally result in
427 // smaller trees down the road.
428 // - It removes large inefficient search trees.
429 //
430 // TODO(user): a strong branching initial start, or allowing a few decision
431 // per nodes might be a better approach.
432 //
433 // TODO(user): It would also be cool to exploit the reason for the LB increase
434 // even more.
435 const int kMaxNumInitialRestarts = 10;
436 const int64_t kNumDecisionsBeforeInitialRestarts = 1000;
437
438 // If some branches already have a good lower bound, no need to call the LP
439 // on those.
440 watcher_->SetStopPropagationCallback([this] {
441 return integer_trail_->LowerBound(objective_var_) > current_objective_lb_;
442 });
443
444 while (!time_limit_->LimitReached() && !shared_response_->ProblemIsSolved()) {
445 VLOG(2) << "LOOP " << sat_solver_->CurrentDecisionLevel();
446
447 // Each time we are back here, we bump the activities of the variable that
448 // are part of the objective lower bound reason.
449 //
450 // Note that this is why we prefer not to increase the lower zero lower
451 // bound of objective_var_ with the tree root lower bound, so we can exploit
452 // more reasons.
453 //
454 // TODO(user): This is slightly different than bumping each time we
455 // push a decision that result in an LB increase. This is also called on
456 // backjump for instance.
457 const IntegerValue obj_diff =
458 integer_trail_->LowerBound(objective_var_) -
459 integer_trail_->LevelZeroLowerBound(objective_var_);
460 if (obj_diff > 0) {
461 std::vector<Literal> reason =
463 objective_var_, integer_trail_->LowerBound(objective_var_)));
464
465 // TODO(user): We also need to update pseudo cost on conflict.
466 pseudo_costs_->UpdateBoolPseudoCosts(reason, obj_diff);
467 sat_decision_->BumpVariableActivities(reason);
468 sat_decision_->UpdateVariableActivityIncrement();
469 }
470
471 // Propagate upward in the tree the new objective lb.
472 if (!current_branch_.empty()) {
473 // Our branch is always greater or equal to the level.
474 // We increase the objective_lb of the current node if needed.
475 {
476 const int current_level = sat_solver_->CurrentDecisionLevel();
477 const IntegerValue current_objective_lb =
478 integer_trail_->LowerBound(objective_var_);
479 if (DEBUG_MODE) {
480 CHECK_LE(current_level, current_branch_.size());
481 for (int i = 0; i < current_level; ++i) {
482 CHECK(!nodes_[current_branch_[i]].is_deleted);
483 CHECK(assignment_.LiteralIsAssigned(
484 nodes_[current_branch_[i]].Decision()));
485 }
486 }
487 if (current_level < current_branch_.size()) {
488 nodes_[current_branch_[current_level]].UpdateObjective(
489 current_objective_lb);
490 }
491
492 // Minor optim: sometimes, because of the LP and cuts, the reason for
493 // objective_var_ only contains lower level literals, so we can exploit
494 // that.
495 //
496 // TODO(user): No point checking that if the objective lb wasn't
497 // assigned at this level.
498 //
499 // TODO(user): Exploit the reasons further.
500 if (current_objective_lb >
501 integer_trail_->LevelZeroLowerBound(objective_var_)) {
502 const std::vector<Literal> reason =
504 objective_var_, current_objective_lb));
505 int max_level = 0;
506 for (const Literal l : reason) {
507 max_level = std::max<int>(
508 max_level,
509 sat_solver_->LiteralTrail().Info(l.Variable()).level);
510 }
511 if (max_level < current_level) {
512 nodes_[current_branch_[max_level]].UpdateObjective(
513 current_objective_lb);
514 }
515 }
516 }
517
518 // Propagate upward any new bounds.
519 for (int level = current_branch_.size(); --level > 0;) {
520 UpdateParentObjective(level);
521 }
522 }
523
524 if (SaveLpBasisOption()) {
525 // We disable LP automatic propagation and only enable it:
526 // - at root node
527 // - when we go to a new branch.
528 lp_constraint_->EnablePropagation(false);
529 }
530
531 // This will import other workers bound if we are back to level zero.
532 // It might also decide to restart.
533 if (!search_helper_->BeforeTakingDecision()) {
534 return sat_solver_->UnsatStatus();
535 }
536
537 // This is the current bound we try to improve. We cache it here to avoid
538 // getting the lock many times and it is also easier to follow the code if
539 // this is assumed constant for one iteration.
540 current_objective_lb_ = shared_response_->GetInnerObjectiveLowerBound();
541 if (!current_branch_.empty()) {
542 nodes_[current_branch_[0]].UpdateObjective(current_objective_lb_);
543 for (int i = 1; i < current_branch_.size(); ++i) {
544 UpdateObjectiveFromParent(i);
545 }
546
547 // If the root lb increased, update global shared objective lb.
548 const IntegerValue bound = nodes_[current_branch_[0]].MinObjective();
549 if (bound > current_objective_lb_) {
550 shared_response_->UpdateInnerObjectiveBounds(
551 absl::StrCat(name_, " (", SmallProgressString(), ") "), bound,
552 integer_trail_->LevelZeroUpperBound(objective_var_));
553 current_objective_lb_ = bound;
554 if (VLOG_IS_ON(3)) DebugDisplayTree(current_branch_[0]);
555 }
556 }
557
558 // Forget the whole tree and restart.
559 // We will do it periodically at the beginning of the search each time we
560 // cross the kNumDecisionsBeforeInitialRestarts decision since the last
561 // restart. This will happen at most kMaxNumInitialRestarts times.
562 if (num_decisions_taken_ >= num_decisions_taken_at_last_restart_ +
563 kNumDecisionsBeforeInitialRestarts &&
564 num_full_restarts_ < kMaxNumInitialRestarts) {
565 VLOG(2) << "lb_tree_search (initial_restart " << SmallProgressString()
566 << ")";
567 if (!FullRestart()) return sat_solver_->UnsatStatus();
568 continue;
569 }
570
571 // Periodic backtrack to level zero so we can import bounds.
572 if (num_decisions_taken_ >=
573 num_decisions_taken_at_last_level_zero_ + 10000) {
574 if (!sat_solver_->ResetToLevelZero()) {
575 return sat_solver_->UnsatStatus();
576 }
577 }
578
579 // Backtrack if needed.
580 //
581 // Our algorithm stop exploring a branch as soon as its objective lower
582 // bound is greater than the root lower bound. We then backtrack to the
583 // first node in the branch that is not yet closed under this bound.
584 //
585 // TODO(user): If we remember how far we can backjump for both true/false
586 // branch, we could be more efficient.
587 while (current_branch_.size() > sat_solver_->CurrentDecisionLevel() + 1 ||
588 (current_branch_.size() > 1 &&
589 nodes_[current_branch_.back()].MinObjective() >
590 current_objective_lb_)) {
591 current_branch_.pop_back();
592 }
593
594 // Backtrack the solver to be in sync with current_branch_.
595 {
596 const int backtrack_level =
597 std::max(0, static_cast<int>(current_branch_.size()) - 1);
598 sat_solver_->Backtrack(backtrack_level);
599 if (!sat_solver_->FinishPropagation()) {
600 return sat_solver_->UnsatStatus();
601 }
602 if (sat_solver_->CurrentDecisionLevel() < backtrack_level) continue;
603 }
604
605 if (sat_solver_->CurrentDecisionLevel() == 0) {
606 if (!LevelZeroLogic()) {
607 return sat_solver_->UnsatStatus();
608 }
609 }
610
611 // Dive: Follow the branch with lowest objective.
612 // Note that we do not creates new nodes here.
613 //
614 // TODO(user): If we have new information and our current objective bound
615 // is higher than any bound in a whole subtree, we might want to just
616 // restart this subtree exploration?
617 while (true) {
618 const int size = current_branch_.size();
619 const int level = sat_solver_->CurrentDecisionLevel();
620
621 // Invariant are tricky:
622 // current_branch_ contains one entry per decision taken + the last one
623 // which we are about to take. If we don't have the last entry, it means
624 // we are about to take a new decision.
625 DCHECK(size == level || size == level + 1);
626 if (size == level) break; // Take new decision.
627
628 const NodeIndex node_index = current_branch_[level];
629 Node& node = nodes_[node_index];
630 DCHECK_GT(node.true_child, node_index);
631 DCHECK_GT(node.false_child, node_index);
632
633 // If the bound of this node is high, restart the main loop..
634 node.UpdateObjective(std::max(
635 current_objective_lb_, integer_trail_->LowerBound(objective_var_)));
636 if (node.MinObjective() > current_objective_lb_) break;
637 DCHECK_EQ(node.MinObjective(), current_objective_lb_) << level;
638
639 // This will be set to the next node index.
640 NodeIndex n;
641 DCHECK(!node.is_deleted);
642 const Literal node_literal = node.Decision();
643
644 // If the variable is already fixed, we bypass the node and connect
645 // its parent directly to the relevant child.
646 if (assignment_.LiteralIsAssigned(node_literal)) {
647 IntegerValue new_lb;
648 if (assignment_.LiteralIsTrue(node_literal)) {
649 n = node.true_child;
650 new_lb = node.true_objective;
651 } else {
652 n = node.false_child;
653 new_lb = node.false_objective;
654 }
655 MarkAsDeletedNodeAndUnreachableSubtree(node);
656
657 // We jump directly to the subnode.
658 // Else we will change the root.
659 current_branch_.pop_back();
660 if (!current_branch_.empty()) {
661 const NodeIndex parent = current_branch_.back();
662 DCHECK(!nodes_[parent].is_deleted);
663 const Literal parent_literal = nodes_[parent].Decision();
664 if (assignment_.LiteralIsTrue(parent_literal)) {
665 nodes_[parent].true_child = n;
666 nodes_[parent].UpdateTrueObjective(new_lb);
667 } else {
668 DCHECK(assignment_.LiteralIsFalse(parent_literal));
669 nodes_[parent].false_child = n;
670 nodes_[parent].UpdateFalseObjective(new_lb);
671 }
672 if (new_lb > current_objective_lb_) {
673 // This is probably not needed.
674 if (n < nodes_.size() && !nodes_[n].IsLeaf()) {
675 current_branch_.push_back(n);
676 nodes_[n].UpdateObjective(new_lb);
677 }
678 break;
679 }
680 } else {
681 if (n >= nodes_.size()) {
682 // We never explored the other branch, so we can just clear all
683 // nodes.
684 num_nodes_in_tree_ = 0;
685 nodes_.clear();
686 } else if (nodes_[n].IsLeaf()) {
687 // Keep the saved basis.
688 num_nodes_in_tree_ = 1;
689 Node root = std::move(nodes_[n]);
690 nodes_.clear();
691 nodes_.push_back(std::move(root));
692 n = NodeIndex(0);
693 } else {
694 // We always make sure the root is at zero.
695 // The root is no longer at zero, that might cause issue.
696 // Cleanup.
697 }
698 }
699 } else {
700 // See if we have better bounds using the current LP state.
701 ExploitReducedCosts(current_branch_[level]);
702 if (node.MinObjective() > current_objective_lb_) break;
703
704 // If both lower bound are the same, we pick the literal branch. We do
705 // that because this is the polarity that was chosen by the SAT
706 // heuristic in the first place. We tried random, it doesn't seems to
707 // work as well.
708 num_decisions_taken_++;
709 const bool choose_true = node.true_objective <= node.false_objective;
710 Literal next_decision;
711 if (choose_true) {
712 n = node.true_child;
713 next_decision = node_literal;
714 } else {
715 n = node.false_child;
716 next_decision = node_literal.Negated();
717 }
718
719 // If we are taking this branch for the first time, we enable the LP and
720 // make sure we solve it before taking the decision. This allow to have
721 // proper pseudo-costs, and also be incremental for the decision we are
722 // about to take.
723 //
724 // We also enable the LP if we have no basis info for this node.
725 if (SaveLpBasisOption() &&
726 (n >= nodes_.size() || !NodeHasBasis(node))) {
727 const auto cleanup =
728 absl::MakeCleanup(UpdateLpIters(&num_lp_iters_save_basis_));
729
730 VLOG(1) << "~~~~";
731 EnableLpAndLoadBestBasis();
732 const int level = sat_solver_->CurrentDecisionLevel();
733 if (!sat_solver_->FinishPropagation()) {
734 return sat_solver_->UnsatStatus();
735 }
736 if (sat_solver_->CurrentDecisionLevel() < level) {
737 node.UpdateObjective(kMaxIntegerValue);
738 break;
739 }
740
741 // The decision might have become assigned, in which case we loop.
742 if (assignment_.LiteralIsAssigned(next_decision)) {
743 continue;
744 }
745
746 SaveLpBasisInto(node);
747
748 // If we are not at the end, disable the LP propagation.
749 if (n < nodes_.size()) {
750 lp_constraint_->EnablePropagation(false);
751 }
752 }
753
754 // Take the decision.
755 const auto cleanup =
756 absl::MakeCleanup(UpdateLpIters(&num_lp_iters_first_branch_));
757 DCHECK(!assignment_.LiteralIsAssigned(next_decision));
758 search_helper_->TakeDecision(next_decision);
759
760 // Conflict?
761 if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
762 MarkBranchAsInfeasible(node, choose_true);
763 break;
764 }
765
766 // Update the proper field and abort the dive if we crossed the
767 // threshold.
768 const IntegerValue lb = integer_trail_->LowerBound(objective_var_);
769 if (choose_true) {
770 node.UpdateTrueObjective(lb);
771 } else {
772 node.UpdateFalseObjective(lb);
773 }
774
775 if (n < nodes_.size()) {
776 nodes_[n].UpdateObjective(lb);
777 } else if (SaveLpBasisOption()) {
778 SaveLpBasisInto(nodes_[CreateNewEmptyNodeIfNeeded()]);
779 }
780
781 if (lb > current_objective_lb_) break;
782 }
783
784 if (VLOG_IS_ON(1)) {
785 shared_response_->LogMessageWithThrottling(
786 "TreeS", absl::StrCat(" (", SmallProgressString(), ")"));
787 }
788
789 if (n < nodes_.size() && !nodes_[n].IsLeaf()) {
790 current_branch_.push_back(n);
791 } else {
792 break;
793 }
794 }
795
796 // If a conflict occurred, we will backtrack.
797 if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
798 continue;
799 }
800
801 // TODO(user): The code is hard to follow. Fix and merge that with test
802 // below.
803 if (!current_branch_.empty()) {
804 const Node& final_node = nodes_[current_branch_.back()];
805 if (assignment_.LiteralIsTrue(final_node.Decision())) {
806 if (final_node.true_objective > current_objective_lb_) {
807 continue;
808 }
809 } else {
810 DCHECK(assignment_.LiteralIsFalse(final_node.Decision()));
811 if (final_node.false_objective > current_objective_lb_) {
812 continue;
813 }
814 }
815 }
816
817 // This test allow to not take a decision when the branch is already closed
818 // (i.e. the true branch or false branch lb is high enough). Adding it
819 // basically changes if we take the decision later when we explore the
820 // branch or right now.
821 //
822 // I feel taking it later is better. It also avoid creating unneeded nodes.
823 // It does change the behavior on a few problem though. For instance on
824 // irp.mps.gz, the search works better without this, whatever the random
825 // seed. Not sure why, maybe it creates more diversity?
826 //
827 // Another difference is that if the search is done and we have a feasible
828 // solution, we will not report it because of this test (except if we are
829 // at the optimal).
830 if (integer_trail_->LowerBound(objective_var_) > current_objective_lb_) {
831 continue;
832 }
833
834 const auto cleanup = absl::MakeCleanup(UpdateLpIters(&num_lp_iters_dive_));
835
836 if (current_branch_.empty()) {
837 VLOG(2) << "DIVE from empty tree";
838 } else {
839 VLOG(2) << "DIVE from " << NodeDebugString(current_branch_.back());
840 }
841
842 if (SaveLpBasisOption() && !lp_constraint_->PropagationIsEnabled()) {
843 // This reuse or create a node to store the basis.
844 const NodeIndex index = CreateNewEmptyNodeIfNeeded();
845
846 EnableLpAndLoadBestBasis();
847 const int level = sat_solver_->CurrentDecisionLevel();
848 if (!sat_solver_->FinishPropagation()) {
849 return sat_solver_->UnsatStatus();
850 }
851
852 // Loop on backtrack or bound improvement.
853 if (sat_solver_->CurrentDecisionLevel() < level) {
854 Node& node = nodes_[index];
855 node.UpdateObjective(kMaxIntegerValue);
856 continue;
857 }
858
859 SaveLpBasisInto(nodes_[index]);
860
861 const IntegerValue obj_lb = integer_trail_->LowerBound(objective_var_);
862 if (obj_lb > current_objective_lb_) {
863 nodes_[index].UpdateObjective(obj_lb);
864 if (!current_branch_.empty()) {
865 Node& parent_node = nodes_[current_branch_.back()];
866 const Literal node_literal = parent_node.Decision();
867 DCHECK(assignment_.LiteralIsAssigned(node_literal));
868 if (assignment_.LiteralIsTrue(node_literal)) {
869 parent_node.UpdateTrueObjective(obj_lb);
870 } else {
871 parent_node.UpdateFalseObjective(obj_lb);
872 }
873 }
874 continue;
875 }
876 }
877
878 // Invariant: The current branch is fully assigned, and the solver is in
879 // sync. And we are not on a "bad" path.
880 const int base_level = sat_solver_->CurrentDecisionLevel();
881 if (DEBUG_MODE) {
882 CHECK_EQ(base_level, current_branch_.size());
883 for (const NodeIndex index : current_branch_) {
884 CHECK(!nodes_[index].is_deleted);
885 const Literal decision = nodes_[index].Decision();
886 if (assignment_.LiteralIsTrue(decision)) {
887 CHECK_EQ(nodes_[index].true_objective, current_objective_lb_);
888 } else {
889 CHECK(assignment_.LiteralIsFalse(decision));
890 CHECK_EQ(nodes_[index].false_objective, current_objective_lb_);
891 }
892 }
893 }
894
895 // We are about to take a new decision, what we will do is dive until
896 // the objective lower bound increase. we will then create a bunch of new
897 // nodes in the tree.
898 //
899 // By analyzing the reason for the increase, we can create less nodes than
900 // if we just followed the initial heuristic.
901 //
902 // TODO(user): In multithread, this change the behavior a lot since we
903 // dive until we beat the best shared bound. Maybe we shouldn't do that.
904 while (true) {
905 // TODO(user): We sometimes branch on the objective variable, this should
906 // probably be avoided.
907 if (sat_solver_->ModelIsUnsat()) return sat_solver_->UnsatStatus();
908 LiteralIndex decision;
909 if (!search_helper_->GetDecision(search_heuristic_, &decision)) {
910 continue;
911 }
912
913 // No new decision: search done.
914 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
915 if (decision == kNoLiteralIndex) {
916 feasible_solution_observer();
917 break;
918 }
919
920 num_decisions_taken_++;
921 if (!search_helper_->TakeDecision(Literal(decision))) {
922 return sat_solver_->UnsatStatus();
923 }
924 if (sat_solver_->CurrentDecisionLevel() < base_level) {
925 // TODO(user): it would be nice to mark some node as infeasible if
926 // this is the case. However this could happen after many decision and
927 // we realize with the lp that one of them should have been fixed
928 // earlier, without any infeasibility in the current branch.
929 break;
930 }
931 if (integer_trail_->LowerBound(objective_var_) > current_objective_lb_) {
932 break;
933 }
934 }
935
936 if (sat_solver_->CurrentDecisionLevel() <= base_level) continue;
937
938 // Analyse the reason for objective increase. Deduce a set of new nodes to
939 // append to the tree.
940 //
941 // TODO(user): Try to minimize the number of decisions?
942 const std::vector<Literal> reason =
944 objective_var_, integer_trail_->LowerBound(objective_var_)));
945 std::vector<Literal> decisions = ExtractDecisions(base_level, reason);
946
947 // Bump activities.
948 sat_decision_->BumpVariableActivities(reason);
949 sat_decision_->BumpVariableActivities(decisions);
950 sat_decision_->UpdateVariableActivityIncrement();
951
952 // Create one node per new decisions.
953 DCHECK_EQ(current_branch_.size(), base_level);
954 for (const Literal d : decisions) {
955 AppendNewNodeToCurrentBranch(d);
956 }
957
958 // TODO(user): We should probably save the basis in more cases.
959 if (SaveLpBasisOption() && decisions.size() == 1) {
960 SaveLpBasisInto(nodes_[CreateNewEmptyNodeIfNeeded()]);
961 }
962
963 // Update the objective of the last node in the branch since we just
964 // improved that.
965 if (!current_branch_.empty()) {
966 Node& n = nodes_[current_branch_.back()];
967 if (assignment_.LiteralIsTrue(n.Decision())) {
968 n.UpdateTrueObjective(integer_trail_->LowerBound(objective_var_));
969 } else {
970 n.UpdateFalseObjective(integer_trail_->LowerBound(objective_var_));
971 }
972 }
973
974 // Reset the solver to a correct state since we have a subset of the
975 // current propagation. We backtrack as little as possible.
976 //
977 // The decision level is the number of decision taken.
978 // Decision()[level] is the decision at that level.
979 int backtrack_level = base_level;
980 DCHECK_LE(current_branch_.size(), sat_solver_->CurrentDecisionLevel());
981 while (backtrack_level < current_branch_.size() &&
982 sat_solver_->Decisions()[backtrack_level].literal.Index() ==
983 nodes_[current_branch_[backtrack_level]].literal_index) {
984 ++backtrack_level;
985 }
986 sat_solver_->Backtrack(backtrack_level);
987
988 // Update bounds with reduced costs info.
989 //
990 // TODO(user): Uses old optimal constraint that we just potentially
991 // backtracked over?
992 //
993 // TODO(user): We could do all at once rather than in O(#decision * #size).
994 for (int i = backtrack_level; i < current_branch_.size(); ++i) {
995 ExploitReducedCosts(current_branch_[i]);
996 }
997 }
998
1000}
1001
1002std::vector<Literal> LbTreeSearch::ExtractDecisions(
1003 int base_level, absl::Span<const Literal> conflict) {
1004 std::vector<int> num_per_level(sat_solver_->CurrentDecisionLevel() + 1, 0);
1005 std::vector<bool> is_marked;
1006 for (const Literal l : conflict) {
1007 const AssignmentInfo& info = trail_->Info(l.Variable());
1008 if (info.level <= base_level) continue;
1009 num_per_level[info.level]++;
1010 if (info.trail_index >= is_marked.size()) {
1011 is_marked.resize(info.trail_index + 1);
1012 }
1013 is_marked[info.trail_index] = true;
1014 }
1015
1016 std::vector<Literal> result;
1017 if (is_marked.empty()) return result;
1018 for (int i = is_marked.size() - 1; i >= 0; --i) {
1019 if (!is_marked[i]) continue;
1020
1021 const Literal l = (*trail_)[i];
1022 const AssignmentInfo& info = trail_->Info(l.Variable());
1023 if (info.level <= base_level) break;
1024 if (num_per_level[info.level] == 1) {
1025 result.push_back(l);
1026 continue;
1027 }
1028
1029 // Expand.
1030 num_per_level[info.level]--;
1031 for (const Literal new_l : trail_->Reason(l.Variable())) {
1032 const AssignmentInfo& new_info = trail_->Info(new_l.Variable());
1033 if (new_info.level <= base_level) continue;
1034 if (is_marked[new_info.trail_index]) continue;
1035 is_marked[new_info.trail_index] = true;
1036 num_per_level[new_info.level]++;
1037 }
1038 }
1039
1040 // We prefer to keep the same order.
1041 std::reverse(result.begin(), result.end());
1042 return result;
1043}
1044
1045LbTreeSearch::NodeIndex LbTreeSearch::CreateNewEmptyNodeIfNeeded() {
1046 NodeIndex n(0);
1047 if (current_branch_.empty()) {
1048 if (nodes_.empty()) {
1049 ++num_nodes_in_tree_;
1050 nodes_.emplace_back(current_objective_lb_);
1051 } else {
1052 DCHECK_EQ(nodes_.size(), 1);
1053 }
1054 } else {
1055 const NodeIndex parent = current_branch_.back();
1056 DCHECK(!nodes_[parent].is_deleted);
1057 const Literal parent_literal = nodes_[parent].Decision();
1058 if (assignment_.LiteralIsTrue(parent_literal)) {
1059 if (nodes_[parent].true_child >= nodes_.size()) {
1060 n = NodeIndex(nodes_.size());
1061 ++num_nodes_in_tree_;
1062 nodes_[parent].true_child = NodeIndex(nodes_.size());
1063 nodes_.emplace_back(current_objective_lb_);
1064 } else {
1065 n = nodes_[parent].true_child;
1066 }
1067 nodes_[parent].UpdateTrueObjective(current_objective_lb_);
1068 } else {
1069 DCHECK(assignment_.LiteralIsFalse(parent_literal));
1070 if (nodes_[parent].false_child >= nodes_.size()) {
1071 n = NodeIndex(nodes_.size());
1072 ++num_nodes_in_tree_;
1073 nodes_[parent].false_child = NodeIndex(nodes_.size());
1074 nodes_.emplace_back(current_objective_lb_);
1075 } else {
1076 n = nodes_[parent].false_child;
1077 }
1078 nodes_[parent].UpdateFalseObjective(current_objective_lb_);
1079 }
1080 }
1081 DCHECK(!nodes_[n].is_deleted);
1082 DCHECK_EQ(nodes_[n].literal_index, kNoLiteralIndex);
1083 return n;
1084}
1085
1086void LbTreeSearch::AppendNewNodeToCurrentBranch(Literal decision) {
1087 NodeIndex n(nodes_.size());
1088 if (current_branch_.empty()) {
1089 if (nodes_.empty()) {
1090 ++num_nodes_in_tree_;
1091 nodes_.emplace_back(current_objective_lb_);
1092 } else {
1093 DCHECK_EQ(nodes_.size(), 1);
1094 n = 0;
1095 }
1096 } else {
1097 const NodeIndex parent = current_branch_.back();
1098 DCHECK(!nodes_[parent].is_deleted);
1099 const Literal parent_literal = nodes_[parent].Decision();
1100 if (assignment_.LiteralIsTrue(parent_literal)) {
1101 if (nodes_[parent].true_child < nodes_.size()) {
1102 n = nodes_[parent].true_child;
1103 } else {
1104 ++num_nodes_in_tree_;
1105 nodes_.emplace_back(current_objective_lb_);
1106 nodes_[parent].true_child = n;
1107 }
1108 nodes_[parent].UpdateTrueObjective(current_objective_lb_);
1109 } else {
1110 DCHECK(assignment_.LiteralIsFalse(parent_literal));
1111 if (nodes_[parent].false_child < nodes_.size()) {
1112 n = nodes_[parent].false_child;
1113 } else {
1114 ++num_nodes_in_tree_;
1115 nodes_.emplace_back(current_objective_lb_);
1116 nodes_[parent].false_child = n;
1117 }
1118 nodes_[parent].UpdateFalseObjective(current_objective_lb_);
1119 }
1120 }
1121 DCHECK_LT(n, nodes_.size());
1122 DCHECK_EQ(nodes_[n].literal_index, kNoLiteralIndex) << " issue " << n;
1123 nodes_[n].SetDecision(decision);
1124 nodes_[n].UpdateObjective(current_objective_lb_);
1125 current_branch_.push_back(n);
1126}
1127
1128// Looking at the reduced costs, we can already have a bound for one of the
1129// branch. Increasing the corresponding objective can save some branches,
1130// and also allow for a more incremental LP solving since we do less back
1131// and forth.
1132//
1133// TODO(user): The code to recover that is a bit convoluted. Alternatively
1134// Maybe we should do a "fast" propagation without the LP in each branch.
1135// That will work as long as we keep these optimal LP constraints around
1136// and propagate them.
1137//
1138// TODO(user): Incorporate this in the heuristic so we choose more Booleans
1139// inside these LP explanations?
1140void LbTreeSearch::ExploitReducedCosts(NodeIndex n) {
1141 if (lp_constraint_ == nullptr) return;
1142
1143 // TODO(user): we could consider earlier constraints instead of just
1144 // looking at the last one, but experiments didn't really show a big
1145 // gain.
1146 const auto& cts = lp_constraint_->OptimalConstraints();
1147 if (cts.empty()) return;
1148 const std::unique_ptr<IntegerSumLE128>& rc = cts.back();
1149
1150 // Note that this return literal EQUIVALENT to the node.literal, not just
1151 // implied by it. We need that for correctness.
1152 int num_tests = 0;
1153 Node& node = nodes_[n];
1154 DCHECK(!node.is_deleted);
1155 const Literal node_literal = node.Decision();
1156 DCHECK(!assignment_.LiteralIsAssigned(node_literal));
1157 for (const IntegerLiteral integer_literal :
1158 integer_encoder_->GetIntegerLiterals(node_literal)) {
1159 // To avoid bad corner case. Not sure it ever triggers.
1160 if (++num_tests > 10) break;
1161
1162 const std::pair<IntegerValue, IntegerValue> bounds =
1163 rc->ConditionalLb(integer_literal, objective_var_);
1164 if (bounds.first > node.false_objective) {
1165 ++num_rc_detected_;
1166 node.UpdateFalseObjective(bounds.first);
1167 }
1168 if (bounds.second > node.true_objective) {
1169 ++num_rc_detected_;
1170 node.UpdateTrueObjective(bounds.second);
1171 }
1172 }
1173}
1174
1175} // namespace sat
1176} // namespace operations_research
IntegerValue size
void SetStopPropagationCallback(std::function< bool()> callback)
Definition integer.h:1592
const InlinedIntegerLiteralVector & GetIntegerLiterals(Literal lit) const
Returns the IntegerLiterals that were associated with the given Literal.
Definition integer.h:584
An helper class to share the code used by the different kind of search.
bool GetDecision(const std::function< BooleanOrIntegerLiteral()> &f, LiteralIndex *decision)
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1717
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1721
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1817
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
Definition integer.h:1812
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit)
Definition integer.h:972
std::vector< Literal > ReasonFor(IntegerLiteral literal) const
Definition integer.cc:1819
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
Explores the search space.
A class that stores the collection of all LP constraints in a model.
int64_t num_lp_changes() const
This can serve as a timestamp to know if a saved basis is out of date.
const std::vector< std::unique_ptr< IntegerSumLE128 > > & OptimalConstraints() const
void UpdateBoolPseudoCosts(absl::Span< const Literal > reason, IntegerValue objective_increase)
void BumpVariableActivities(absl::Span< const Literal > literals)
void Backtrack(int target_level)
ABSL_MUST_USE_RESULT bool ResetToLevelZero()
const std::vector< Decision > & Decisions() const
Definition sat_solver.h:385
ABSL_MUST_USE_RESULT bool FinishPropagation()
const Trail & LiteralTrail() const
Definition sat_solver.h:387
void LogMessageWithThrottling(const std::string &prefix, const std::string &message)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
Updates the inner objective bounds.
absl::Span< const Literal > Reason(BooleanVariable var, int64_t conflict_id=-1) const
Definition sat_base.h:680
const AssignmentInfo & Info(BooleanVariable var) const
Definition sat_base.h:463
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:191
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:185
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:188
void push_back(const value_type &val)
int64_t value
GRBmodel * model
int index
const bool DEBUG_MODE
Definition macros.h:24
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> IntegerValueSelectionHeuristic(std::function< BooleanOrIntegerLiteral()> var_selection_heuristic, Model *model)
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
Definition util.cc:49
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
Returns the BooleanOrIntegerLiteral advised by the underlying SAT solver.
std::function< BooleanOrIntegerLiteral()> LpPseudoCostHeuristic(Model *model)
std::function< BooleanOrIntegerLiteral()> MostFractionalHeuristic(Model *model)
Choose the variable with most fractional LP value.
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()> > heuristics)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t bound
Information about a variable assignment.
Definition sat_base.h:242
int32_t trail_index
The index of this assignment in the trail.
Definition sat_base.h:263
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1667
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1673
std::function< BooleanOrIntegerLiteral()> fixed_search
Fixed search, built from above building blocks.