Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
lb_tree_search.h
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
14#ifndef OR_TOOLS_SAT_LB_TREE_SEARCH_H_
15#define OR_TOOLS_SAT_LB_TREE_SEARCH_H_
16
17#include <stdint.h>
18
19#include <algorithm>
20#include <functional>
21#include <limits>
22#include <string>
23#include <vector>
24
25#include "absl/strings/string_view.h"
26#include "absl/time/time.h"
27#include "absl/types/span.h"
29#include "ortools/sat/integer.h"
32#include "ortools/sat/model.h"
36#include "ortools/sat/sat_parameters.pb.h"
39#include "ortools/sat/util.h"
42
43namespace operations_research {
44namespace sat {
45
46// Implement a "classic" MIP tree search by having an exhaustive list of open
47// nodes.
48//
49// The goal of this subsolver is to improve the objective lower bound. It is
50// meant to be used in a multi-thread portfolio, and as such it really do not
51// care about finding solution. It is all about improving the lower bound.
52//
53// TODO(user): What this is doing is really similar to asking a SAT solver if
54// the current objective lower bound is reachable by solving a SAT problem.
55// However, this code handle on the side all the "conflict" of the form
56// objective > current_lb. As a result, when it is UNSAT, we can bump the lower
57// bound by a bigger amount than one. We also do not completely loose everything
58// learned so far for the next iteration.
60 public:
61 explicit LbTreeSearch(Model* model);
62
63 // Explores the search space.
65 const std::function<void()>& feasible_solution_observer);
66
67 private:
68 // Code a binary tree.
70 struct Node {
71 explicit Node(IntegerValue lb) : true_objective(lb), false_objective(lb) {}
72
73 // The objective lower bound at this node.
74 IntegerValue MinObjective() const {
75 return std::min(true_objective, false_objective);
76 }
77
78 // Invariant: the objective bounds only increase.
79 void UpdateObjective(IntegerValue v) {
80 true_objective = std::max(true_objective, v);
81 false_objective = std::max(false_objective, v);
82 }
83 void UpdateTrueObjective(IntegerValue v) {
84 true_objective = std::max(true_objective, v);
85 }
86 void UpdateFalseObjective(IntegerValue v) {
87 false_objective = std::max(false_objective, v);
88 }
89
90 // Should be called only once.
91 void SetDecision(Literal l) {
92 DCHECK(!is_deleted);
93 DCHECK_EQ(literal_index, kNoLiteralIndex);
94 literal_index = l.Index();
95 }
96
97 Literal Decision() const {
98 DCHECK(!is_deleted);
99 DCHECK_NE(literal_index, kNoLiteralIndex);
100 return sat::Literal(literal_index);
101 }
102
103 bool IsLeaf() const { return literal_index == kNoLiteralIndex; }
104
105 // The decision for the true and false branch under this node.
106 // Initially this is kNoLiteralIndex until SetDecision() is called.
107 LiteralIndex literal_index = kNoLiteralIndex;
108
109 // The objective lower bound in both branches.
110 IntegerValue true_objective;
111 IntegerValue false_objective;
112
113 // Points to adjacent nodes in the tree. Large if no connection.
114 NodeIndex true_child = NodeIndex(std::numeric_limits<int32_t>::max());
115 NodeIndex false_child = NodeIndex(std::numeric_limits<int32_t>::max());
116
117 // Indicates if this nodes was removed from the tree.
118 bool is_deleted = false;
119
120 // Experimental. Store the optimal basis at each node.
121 int64_t basis_timestamp;
122 glop::BasisState basis;
123 };
124
125 // Regroup some logic done when we are back at level zero in Search().
126 // Returns false if UNSAT.
127 bool LevelZeroLogic();
128
129 // Returns true if we save/load LP basis.
130 // Note that when this is true we also do not solve the LP as often.
131 bool SaveLpBasisOption() const {
132 return lp_constraint_ != nullptr &&
133 parameters_.save_lp_basis_in_lb_tree_search();
134 }
135
136 // Display the current tree, this is mainly here to investigate ideas to
137 // improve the code.
138 std::string NodeDebugString(NodeIndex node) const;
139 void DebugDisplayTree(NodeIndex root) const;
140
141 // Updates the objective of the node in the current branch at level n from
142 // the one at level n - 1.
143 void UpdateObjectiveFromParent(int level);
144
145 // Updates the objective of the node in the current branch at level n - 1 from
146 // the one at level n.
147 void UpdateParentObjective(int level);
148
149 // Returns false on conflict.
150 bool FullRestart();
151
152 // Loads any known basis that is the closest to the current branch.
153 void EnableLpAndLoadBestBasis();
154 void SaveLpBasisInto(Node& node);
155 bool NodeHasUpToDateBasis(const Node& node) const;
156 bool NodeHasBasis(const Node& node) const;
157
158 // Mark the given node as deleted. Its literal is assumed to be set. We also
159 // delete the subtree that is not longer relevant.
160 void MarkAsDeletedNodeAndUnreachableSubtree(Node& node);
161 void MarkBranchAsInfeasible(Node& node, bool true_branch);
162 void MarkSubtreeAsDeleted(NodeIndex root);
163
164 // Create a new node at the end of the current branch.
165 // This assume the last decision in the branch is assigned.
166 NodeIndex CreateNewEmptyNodeIfNeeded();
167 void AppendNewNodeToCurrentBranch(Literal decision);
168
169 // Update the bounds on the given nodes by using reduced costs if possible.
170 void ExploitReducedCosts(NodeIndex n);
171
172 // Returns a small number of decision needed to reach the same conflict.
173 // We basically reduce the number of decision at each level to 1.
174 std::vector<Literal> ExtractDecisions(int base_level,
175 absl::Span<const Literal> conflict);
176
177 // Used in the solve logs.
178 std::string SmallProgressString() const;
179
180 // Save the current number of iterations on creation and add the difference
181 // to the counter when the returned function is called. This is meant to
182 // be used with:
183 // const auto cleanup = absl::MakeCleanup(UpdateLpIters(&counter));
184 std::function<void()> UpdateLpIters(int64_t* counter);
185
186 // Model singleton class used here.
187 const std::string name_;
188 TimeLimit* time_limit_;
189 ModelRandomGenerator* random_;
190 SatSolver* sat_solver_;
191 IntegerEncoder* integer_encoder_;
192 Trail* trail_;
193 const VariablesAssignment& assignment_;
194 IntegerTrail* integer_trail_;
195 GenericLiteralWatcher* watcher_;
196 SharedResponseManager* shared_response_;
197 PseudoCosts* pseudo_costs_;
198 SatDecisionPolicy* sat_decision_;
199 IntegerSearchHelper* search_helper_;
200 IntegerVariable objective_var_;
201 const SatParameters& parameters_;
202
203 // This can stay null. Otherwise it will be the lp constraint with
204 // objective_var_ as objective.
205 LinearProgrammingConstraint* lp_constraint_ = nullptr;
206
207 // We temporarily cache the shared_response_ objective lb here.
208 IntegerValue current_objective_lb_;
209
210 // Memory for all the nodes.
211 int num_nodes_in_tree_ = 0;
213
214 // The list of nodes in the current branch, in order from the root.
215 std::vector<NodeIndex> current_branch_;
216
217 // Our heuristic used to explore the tree. See code for detail.
218 std::function<BooleanOrIntegerLiteral()> search_heuristic_;
219
220 int64_t num_rc_detected_ = 0;
221
222 // Counts the number of decisions we are taking while exploring the search
223 // tree.
224 int64_t num_decisions_taken_ = 0;
225
226 // Counts number of lp iterations at various places.
227 int64_t num_lp_iters_at_level_zero_ = 0;
228 int64_t num_lp_iters_save_basis_ = 0;
229 int64_t num_lp_iters_first_branch_ = 0;
230 int64_t num_lp_iters_dive_ = 0;
231
232 // Used to trigger the initial restarts and imports.
233 int num_full_restarts_ = 0;
234 int64_t num_decisions_taken_at_last_restart_ = 0;
235 int64_t num_decisions_taken_at_last_level_zero_ = 0;
236
237 // Count the number of time we are back to decision level zero.
238 int64_t num_back_to_root_node_ = 0;
239};
240
241} // namespace sat
242} // namespace operations_research
243
244#endif // OR_TOOLS_SAT_LB_TREE_SEARCH_H_
An helper class to share the code used by the different kind of search.
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
Explores the search space.
LiteralIndex Index() const
Definition sat_base.h:91
GRBmodel * model
const LiteralIndex kNoLiteralIndex(-1)
In SWIG mode, we don't want anything besides these top-level includes.
#define DEFINE_STRONG_INDEX_TYPE(index_type_name)