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"
38 #include "ortools/sat/sat_parameters.pb.h"
59 parameters_(*
model->GetOrCreate<SatParameters>()) {
66 CHECK(objective !=
nullptr);
74 if (lp->ObjectiveVariable() == objective_var_) {
86 last_logging_time_ = absl::Now();
89 void LbTreeSearch::UpdateParentObjective(
int level) {
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());
100 CHECK_EQ(parent.false_child, child_index);
101 parent.UpdateFalseObjective(child.MinObjective());
105 void LbTreeSearch::UpdateObjectiveFromParent(
int level) {
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);
117 CHECK_EQ(parent.false_child, child_index);
118 child.UpdateObjective(parent.false_objective);
122 void LbTreeSearch::DebugDisplayTree(
NodeIndex root)
const {
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());
130 std::vector<NodeIndex> to_explore = {root};
131 while (!to_explore.empty()) {
133 to_explore.pop_back();
136 const Node& node = nodes_[n];
138 std::string s(level[n],
' ');
139 absl::StrAppend(&s,
"#", n.value());
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;
147 absl::StrAppend(&s,
" [t:## ", shifted_lb(node.true_objective),
"]");
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;
155 absl::StrAppend(&s,
" [f:## ", shifted_lb(node.false_objective),
"]");
159 LOG(INFO) <<
"num_nodes: " << num_nodes;
166 bool LbTreeSearch::FullRestart() {
167 ++num_full_restarts_;
168 num_decisions_taken_at_last_restart_ = num_decisions_taken_;
169 num_nodes_in_tree_ = 0;
171 current_branch_.clear();
175 void LbTreeSearch::MarkAsDeletedNodeAndUnreachableSubtree(Node& node) {
176 --num_nodes_in_tree_;
177 node.is_deleted =
true;
179 MarkSubtreeAsDeleted(node.false_child);
181 MarkSubtreeAsDeleted(node.true_child);
185 void LbTreeSearch::MarkSubtreeAsDeleted(
NodeIndex root) {
186 std::vector<NodeIndex> to_delete{root};
187 for (
int i = 0; i < to_delete.size(); ++i) {
189 if (n >= nodes_.
size())
continue;
191 --num_nodes_in_tree_;
192 nodes_[n].is_deleted =
true;
194 to_delete.
push_back(nodes_[n].true_child);
195 to_delete.push_back(nodes_[n].false_child);
199 std::string LbTreeSearch::SmallProgressString()
const {
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_);
207 const std::function<
void()>& feasible_solution_observer) {
231 const int kMaxNumInitialRestarts = 10;
232 const int64_t kNumDecisionsBeforeInitialRestarts = 1000;
243 return integer_trail_->
LowerBound(objective_var_) > current_objective_lb_;
247 if (!current_branch_.empty()) {
252 CHECK_GE(current_branch_.size(), current_level);
253 for (
int i = 0; i < current_level; ++i) {
255 nodes_[current_branch_[i]].literal));
257 if (current_level < current_branch_.size()) {
258 nodes_[current_branch_[current_level]].UpdateObjective(
270 if (integer_trail_->
LowerBound(objective_var_) >
272 const std::vector<Literal> reason =
274 objective_var_, integer_trail_->
LowerBound(objective_var_)));
276 for (
const Literal l : reason) {
277 max_level = std::max<int>(
281 if (max_level < current_level) {
282 nodes_[current_branch_[max_level]].UpdateObjective(
289 for (
int level = current_branch_.size(); --level > 0;) {
290 UpdateParentObjective(level);
292 nodes_[current_branch_[0]].UpdateObjective(current_objective_lb_);
293 for (
int level = 1; level < current_branch_.size(); ++level) {
294 UpdateObjectiveFromParent(level);
298 const IntegerValue
bound = nodes_[current_branch_[0]].MinObjective();
299 if (
bound > current_objective_lb_) {
301 absl::StrCat(
"lb_tree_search ", SmallProgressString()),
bound,
303 current_objective_lb_ =
bound;
304 if (
VLOG_IS_ON(3)) DebugDisplayTree(current_branch_[0]);
318 if (integer_trail_->
LowerBound(objective_var_) >
320 std::vector<Literal> reason =
322 objective_var_, integer_trail_->
LowerBound(objective_var_)));
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();
347 (current_branch_.size() > 1 &&
348 nodes_[current_branch_.back()].MinObjective() >
349 current_objective_lb_)) {
350 current_branch_.pop_back();
355 int backtrack_level =
356 std::max(0,
static_cast<int>(current_branch_.size()) - 1);
359 if (num_decisions_taken_ >=
360 num_decisions_taken_at_last_level_zero_ + 10000) {
371 ++num_back_to_root_node_;
372 num_decisions_taken_at_last_level_zero_ = num_decisions_taken_;
389 const IntegerValue latest_lb =
392 int num_nodes_with_lower_objective = 0;
393 for (
const Node& node : nodes_) {
394 if (node.is_deleted)
continue;
396 if (node.MinObjective() < latest_lb) num_nodes_with_lower_objective++;
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();
415 const int level = current_branch_.size() - 1;
417 Node& node = nodes_[current_branch_[level]];
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;
432 new_lb = node.true_objective;
434 n = node.false_child;
435 new_lb = node.false_objective;
437 MarkAsDeletedNodeAndUnreachableSubtree(node);
441 current_branch_.pop_back();
442 if (!current_branch_.empty()) {
443 const NodeIndex parent = current_branch_.back();
445 nodes_[parent].true_child = n;
446 nodes_[parent].UpdateTrueObjective(new_lb);
449 nodes_[parent].literal));
450 nodes_[parent].false_child = n;
451 nodes_[parent].UpdateFalseObjective(new_lb);
453 if (nodes_[parent].MinObjective() > current_objective_lb_)
break;
457 ExploitReducedCosts(current_branch_[level]);
463 num_decisions_taken_++;
464 const bool choose_true = node.true_objective <= node.false_objective;
469 n = node.false_child;
485 const IntegerValue lb = integer_trail_->
LowerBound(objective_var_);
487 node.UpdateTrueObjective(lb);
489 node.UpdateFalseObjective(lb);
491 if (lb > current_objective_lb_)
break;
495 "TreeS", SmallProgressString(),
496 parameters_.log_frequency_in_seconds(), &last_logging_time_);
498 if (n < nodes_.
size()) {
499 current_branch_.push_back(n);
523 if (integer_trail_->
LowerBound(objective_var_) > current_objective_lb_) {
540 const LiteralIndex decision =
546 feasible_solution_observer();
550 num_decisions_taken_++;
555 if (integer_trail_->
LowerBound(objective_var_) > current_objective_lb_) {
565 const std::vector<Literal> reason =
567 objective_var_, integer_trail_->
LowerBound(objective_var_)));
568 std::vector<Literal> decisions = ExtractDecisions(base_level, reason);
576 CHECK_EQ(current_branch_.size(), base_level);
577 for (
const Literal d : decisions) {
578 AppendNewNodeToCurrentBranch(d);
583 if (!current_branch_.empty()) {
584 Node& n = nodes_[current_branch_.back()];
586 n.UpdateTrueObjective(integer_trail_->
LowerBound(objective_var_));
588 n.UpdateFalseObjective(integer_trail_->
LowerBound(objective_var_));
597 int backtrack_level = base_level;
599 while (backtrack_level < current_branch_.size() &&
600 sat_solver_->
Decisions()[backtrack_level].literal ==
601 nodes_[current_branch_[backtrack_level]].literal) {
612 for (
int i = backtrack_level; i < current_branch_.size(); ++i) {
613 ExploitReducedCosts(current_branch_[i]);
620 std::vector<Literal> LbTreeSearch::ExtractDecisions(
621 int base_level,
const std::vector<Literal>& conflict) {
623 std::vector<bool> is_marked;
624 for (
const Literal l : conflict) {
626 if (info.
level <= base_level)
continue;
627 num_per_level[info.
level]++;
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;
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) {
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]++;
659 std::reverse(result.begin(), result.end());
663 void LbTreeSearch::AppendNewNodeToCurrentBranch(Literal decision) {
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();
670 nodes_[parent].true_child = n;
671 nodes_[parent].UpdateTrueObjective(nodes_.
back().MinObjective());
674 nodes_[parent].false_child = n;
675 nodes_[parent].UpdateFalseObjective(nodes_.
back().MinObjective());
678 current_branch_.push_back(n);
693 void LbTreeSearch::ExploitReducedCosts(
NodeIndex n) {
694 if (lp_constraint_ ==
nullptr)
return;
700 if (cts.empty())
return;
701 const std::unique_ptr<IntegerSumLE>& rc = cts.back();
706 Node& node = nodes_[n];
708 for (
const IntegerLiteral integer_literal :
713 if (++num_tests > 10)
break;
715 const std::pair<IntegerValue, IntegerValue>
bounds =
716 rc->ConditionalLb(integer_literal, objective_var_);
717 if (
bounds.first > node.false_objective) {
719 node.UpdateFalseObjective(
bounds.first);
721 if (
bounds.second > node.true_objective) {
723 node.UpdateTrueObjective(
bounds.second);
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...
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
void SetStopPropagationCallback(std::function< bool()> callback)
const InlinedIntegerLiteralVector & GetIntegerLiterals(Literal lit) const
bool BeforeTakingDecision()
bool TakeDecision(Literal decision)
LiteralIndex GetDecision(const std::function< BooleanOrIntegerLiteral()> &f)
bool IsCurrentlyIgnored(IntegerVariable i) const
std::vector< Literal > ReasonFor(IntegerLiteral literal) const
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
IntegerValue LowerBound(IntegerVariable i) const
LbTreeSearch(Model *model)
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.
void UpdateVariableActivityIncrement()
void BumpVariableActivities(const std::vector< Literal > &literals)
const Trail & LiteralTrail() const
Status UnsatStatus() const
const VariablesAssignment & Assignment() const
void Backtrack(int target_level)
bool RestoreSolverToAssumptionLevel()
int CurrentDecisionLevel() const
const std::vector< Decision > & Decisions() const
bool ProblemIsSolved() const
void LogPeriodicMessage(const std::string &prefix, const std::string &message, double frequency_seconds, absl::Time *last_logging_time)
IntegerValue GetInnerObjectiveLowerBound()
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
const AssignmentInfo & Info(BooleanVariable var) const
absl::Span< const Literal > Reason(BooleanVariable var) const
bool LiteralIsAssigned(Literal literal) const
bool LiteralIsTrue(Literal literal) const
bool LiteralIsFalse(Literal literal) const
SharedBoundsManager * bounds
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.
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
IntegerVariable objective_var
#define VLOG(verboselevel)
#define VLOG_IS_ON(verboselevel)