Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
work_assignment.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_WORK_ASSIGNMENT_H_
15#define OR_TOOLS_SAT_WORK_ASSIGNMENT_H_
16
17#include <stdint.h>
18
19#include <array>
20#include <cmath>
21#include <deque>
22#include <functional>
23#include <limits>
24#include <memory>
25#include <optional>
26#include <string>
27#include <utility>
28#include <vector>
29
30#include "absl/base/thread_annotations.h"
31#include "absl/container/flat_hash_map.h"
32#include "absl/container/flat_hash_set.h"
33#include "absl/container/node_hash_map.h"
34#include "absl/log/check.h"
35#include "absl/synchronization/mutex.h"
36#include "absl/types/span.h"
39#include "ortools/sat/integer.h"
41#include "ortools/sat/model.h"
44#include "ortools/sat/sat_parameters.pb.h"
47#include "ortools/sat/util.h"
51
53
55 public:
56 ProtoLiteral() = default;
57 ProtoLiteral(int var, IntegerValue lb) : proto_var_(var), lb_(lb) {}
59 return ProtoLiteral(NegatedRef(proto_var_), -lb_ + 1);
60 }
61 bool operator==(const ProtoLiteral& other) const {
62 return proto_var_ == other.proto_var_ && lb_ == other.lb_;
63 }
64 bool operator!=(const ProtoLiteral& other) const { return !(*this == other); }
65 template <typename H>
66 friend H AbslHashValue(H h, const ProtoLiteral& literal) {
67 return H::combine(std::move(h), literal.proto_var_, literal.lb_);
68 }
69
70 // Note you should only decode integer literals at the root level.
72 static std::optional<ProtoLiteral> Encode(Literal, CpModelMapping*,
74
75 private:
76 IntegerLiteral DecodeInteger(CpModelMapping*) const;
77 static std::optional<ProtoLiteral> EncodeInteger(IntegerLiteral,
79
80 int proto_var_ = std::numeric_limits<int>::max();
81 IntegerValue lb_ = kMaxIntegerValue;
82};
83
84// ProtoTrail acts as an intermediate datastructure that can be synced
85// with the shared tree and the local Trail as appropriate.
86// It's intended that you sync a ProtoTrail with the tree on restart or when
87// a subtree is closed, and with the local trail after propagation.
88// Specifically it stores objective lower bounds, and literals that have been
89// branched on and later proven to be implied by the prior decisions (i.e. they
90// can be enqueued at this level).
91// TODO(user): It'd be good to store an earlier level at which
92// implications may be propagated.
94 public:
95 // Adds a new assigned level to the trail.
96 void PushLevel(const ProtoLiteral& decision, IntegerValue objective_lb,
97 int node_id);
98
99 // Asserts that the decision at `level` is implied by earlier decisions.
100 void SetLevelImplied(int level);
101
102 // Clear the trail, removing all levels.
103 void Clear();
104
105 // Set a lower bound on the objective at level.
106 void SetObjectiveLb(int level, IntegerValue objective_lb);
107
108 // Returns the maximum decision level stored in the trail.
109 int MaxLevel() const { return decision_indexes_.size(); }
110
111 // Returns the decision assigned at `level`.
112 ProtoLiteral Decision(int level) const {
113 CHECK_GE(level, 1);
114 CHECK_LE(level, decision_indexes_.size());
115 return literals_[decision_indexes_[level - 1]];
116 }
117
118 // Returns the node ids for decisions and implications at `level`.
119 absl::Span<const int> NodeIds(int level) const;
120
121 // Returns literals which may be propagated at `level`, this does not include
122 // the decision.
123 absl::Span<const ProtoLiteral> Implications(int level) const;
124 void AddImplication(int level, ProtoLiteral implication) {
125 auto it = implication_level_.find(implication);
126 if (it != implication_level_.end() && it->second <= level) return;
127 MutableImplications(level).push_back(implication);
128 implication_level_[implication] = level;
129 }
130
131 IntegerValue ObjectiveLb(int level) const {
132 CHECK_GE(level, 1);
133 return level_to_objective_lbs_[level - 1];
134 }
135
136 absl::Span<const ProtoLiteral> Literals() const { return literals_; }
137
138 const std::vector<ProtoLiteral>& TargetPhase() const { return target_phase_; }
139 void SetPhase(absl::Span<const ProtoLiteral> phase) {
140 target_phase_.clear();
141 for (const ProtoLiteral& lit : phase) {
142 if (implication_level_.contains(lit)) return;
143 target_phase_.push_back(lit);
144 }
145 }
146
147 private:
148 std::vector<ProtoLiteral>& MutableImplications(int level) {
149 return implications_[level - 1];
150 }
151 // Parallel vectors encoding the literals and node ids on the trail.
152 std::vector<ProtoLiteral> literals_;
153 std::vector<int> node_ids_;
154
155 // Extra implications that can be propagated at each level but were never
156 // branches in the shared tree.
157 std::vector<std::vector<ProtoLiteral>> implications_;
158 absl::flat_hash_map<ProtoLiteral, int> implication_level_;
159
160 // The index in the literals_/node_ids_ vectors for the start of each level.
161 std::vector<int> decision_indexes_;
162
163 // The objective lower bound of each level.
164 std::vector<IntegerValue> level_to_objective_lbs_;
165
166 std::vector<ProtoLiteral> target_phase_;
167};
168
169// Experimental thread-safe class for managing work assignments between workers.
170// This API is intended to investigate Graeme Gange & Peter Stuckey's proposal
171// "Scalable Parallelization of Learning Solvers".
172// The core idea of this implementation is that workers can maintain several
173// ProtoTrails, and periodically replace the "worst" one.
174// With 1 assignment per worker, this leads to something very similar to
175// Embarassingly Parallel Search.
177 public:
178 explicit SharedTreeManager(Model* model);
180
181 int NumWorkers() const { return num_workers_; }
182 int NumNodes() const ABSL_LOCKS_EXCLUDED(mu_);
183
184 // Syncs the state of path with the shared search tree.
185 // Clears `path` and returns false if the assigned subtree is closed or a
186 // restart has invalidated the path.
187 bool SyncTree(ProtoTrail& path) ABSL_LOCKS_EXCLUDED(mu_);
188
189 // Assigns a path prefix that the worker should explore.
190 void ReplaceTree(ProtoTrail& path);
191
192 // Asserts that the subtree in path up to `level` contains no improving
193 // solutions. Clears path.
194 void CloseTree(ProtoTrail& path, int level);
195
196 // Called by workers in order to split the shared tree.
197 // `path` may or may not be extended by one level, branching on `decision`.
198 void ProposeSplit(ProtoTrail& path, ProtoLiteral decision);
199
200 void Restart() {
201 absl::MutexLock l(&mu_);
202 RestartLockHeld();
203 }
204
205 private:
206 // Because it is quite difficult to get a flat_hash_set to release memory,
207 // we store info we need only for open nodes implications via a unique_ptr.
208 // Note to simplify code, the root will always have a NodeTrailInfo after it
209 // is closed.
210 struct NodeTrailInfo {
211 absl::flat_hash_set<ProtoLiteral> implications;
212 // This is only non-empty for nodes where all but one descendent is closed
213 // (i.e. mostly leaves).
214 std::vector<ProtoLiteral> phase;
215 };
216 struct Node {
217 ProtoLiteral literal;
218 IntegerValue objective_lb = kMinIntegerValue;
219 Node* parent = nullptr;
220 std::array<Node*, 2> children = {nullptr, nullptr};
221 // A node's id is related to its index in `nodes_`, see `node_id_offset_`.
222 int id;
223 bool closed = false;
224 bool implied = false;
225 // Only set for open, non-implied nodes.
226 std::unique_ptr<NodeTrailInfo> trail_info;
227 };
228 bool IsValid(const ProtoTrail& path) const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
229 Node* GetSibling(Node* node) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
230 // Returns the NodeTrailInfo for `node` or it's closest non-closed,
231 // non-implied ancestor. `node` must be valid, never returns nullptr.
232 NodeTrailInfo* GetTrailInfo(Node* node);
233 void Split(std::vector<std::pair<Node*, int>>& nodes, ProtoLiteral lit)
234 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
235 Node* MakeSubtree(Node* parent, ProtoLiteral literal)
236 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
237 void ProcessNodeChanges() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
238 std::vector<std::pair<Node*, int>> GetAssignedNodes(const ProtoTrail& path)
239 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
240 void AssignLeaf(ProtoTrail& path, Node* leaf)
241 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
242 void RestartLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
243 std::string ShortStatus() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
244
245 mutable absl::Mutex mu_;
246 const SatParameters& params_;
247 const int num_workers_;
248 SharedResponseManager* const shared_response_manager_;
249
250 // Stores the node id of the root, this is used to handle global restarts.
251 int node_id_offset_ ABSL_GUARDED_BY(mu_) = 0;
252
253 // Stores the nodes in the search tree.
254 std::deque<Node> nodes_ ABSL_GUARDED_BY(mu_);
255 std::vector<Node*> unassigned_leaves_ ABSL_GUARDED_BY(mu_);
256
257 // How many splits we should generate now to keep the desired number of
258 // leaves.
259 int num_splits_wanted_ ABSL_GUARDED_BY(mu_);
260
261 // We limit the total nodes generated per restart to cap the RAM usage and
262 // communication overhead. If we exceed this, workers become portfolio
263 // workers when no unassigned leaves are available.
264 const int max_nodes_;
265 int num_leaves_assigned_ ABSL_GUARDED_BY(mu_) = 0;
266
267 // Temporary vectors used to maintain the state of the tree when nodes are
268 // closed and/or children are updated.
269 std::vector<Node*> to_close_ ABSL_GUARDED_BY(mu_);
270 std::vector<Node*> to_update_ ABSL_GUARDED_BY(mu_);
271
272 int64_t num_restarts_ ABSL_GUARDED_BY(mu_) = 0;
273 int64_t num_syncs_since_restart_ ABSL_GUARDED_BY(mu_) = 0;
274 int num_closed_nodes_ ABSL_GUARDED_BY(mu_) = 0;
275};
276
278 public:
279 explicit SharedTreeWorker(Model* model);
282
284 const std::function<void()>& feasible_solution_observer);
285
286 private:
287 // Syncs the assigned tree with the local trail, ensuring that any new
288 // implications are synced. This is a noop if the search is deeper than the
289 // assigned tree. Returns false if the problem is unsat.
290 bool SyncWithLocalTrail();
291 bool SyncWithSharedTree();
292 Literal DecodeDecision(ProtoLiteral literal);
293 std::optional<ProtoLiteral> EncodeDecision(Literal decision);
294 bool NextDecision(LiteralIndex* decision_index);
295 void MaybeProposeSplit();
296 bool ShouldReplaceSubtree();
297
298 // Add any implications to the clause database for the current level.
299 // Return true if any new information was added.
300 bool AddImplications();
301 bool AddDecisionImplication(Literal literal, int level);
302
303 const std::vector<Literal>& DecisionReason(int level);
304
305 SatParameters* parameters_;
306 SharedResponseManager* shared_response_;
307 TimeLimit* time_limit_;
308 SharedTreeManager* manager_;
309 CpModelMapping* mapping_;
310 SatSolver* sat_solver_;
311 Trail* trail_;
312 IntegerTrail* integer_trail_;
313 IntegerEncoder* encoder_;
314 const ObjectiveDefinition* objective_;
315 ModelRandomGenerator* random_;
316 IntegerSearchHelper* helper_;
317 SearchHeuristics* heuristics_;
318 SatDecisionPolicy* decision_policy_;
319 RestartPolicy* restart_policy_;
320 LevelZeroCallbackHelper* level_zero_callbacks_;
321 RevIntRepository* reversible_int_repository_;
322
323 int64_t num_restarts_ = 0;
324 int64_t num_trees_ = 0;
325
326 ProtoTrail assigned_tree_;
327 std::vector<Literal> assigned_tree_literals_;
328 std::vector<std::vector<Literal>> assigned_tree_implications_;
329 // How many restarts had happened when the current tree was assigned?
330 int64_t tree_assignment_restart_ = -1;
331
332 // True if the last decision may split the assigned tree and has not yet been
333 // proposed to the SharedTreeManager.
334 // We propagate the decision before sharing with the SharedTreeManager so we
335 // don't share any decision that immediately leads to conflict.
336 bool new_split_available_ = false;
337
338 std::vector<Literal> reason_;
339 // Stores the average LBD of learned clauses for each tree assigned since it
340 // was assigned.
341 // If a tree has worse LBD than the average over the last few trees we replace
342 // the tree.
343 RunningAverage assigned_tree_lbds_;
344
345 // Stores the trail index of the last implication added to assigned_tree_.
346 int reversible_trail_index_ = 0;
347 // Stores the number of implications processed for each level in
348 // assigned_tree_.
349 std::deque<int> rev_num_processed_implications_;
350};
351
352} // namespace operations_research::sat
353
354#endif // OR_TOOLS_SAT_WORK_ASSIGNMENT_H_
---------------— Search class --------------—
An helper class to share the code used by the different kind of search.
static std::optional< ProtoLiteral > Encode(Literal, CpModelMapping *, IntegerEncoder *)
friend H AbslHashValue(H h, const ProtoLiteral &literal)
Literal Decode(CpModelMapping *, IntegerEncoder *) const
Note you should only decode integer literals at the root level.
ProtoLiteral(int var, IntegerValue lb)
bool operator==(const ProtoLiteral &other) const
bool operator!=(const ProtoLiteral &other) const
int MaxLevel() const
Returns the maximum decision level stored in the trail.
void SetPhase(absl::Span< const ProtoLiteral > phase)
void Clear()
Clear the trail, removing all levels.
ProtoLiteral Decision(int level) const
Returns the decision assigned at level.
void SetObjectiveLb(int level, IntegerValue objective_lb)
Set a lower bound on the objective at level.
absl::Span< const ProtoLiteral > Implications(int level) const
absl::Span< const int > NodeIds(int level) const
Returns the node ids for decisions and implications at level.
void AddImplication(int level, ProtoLiteral implication)
IntegerValue ObjectiveLb(int level) const
void PushLevel(const ProtoLiteral &decision, IntegerValue objective_lb, int node_id)
Adds a new assigned level to the trail.
absl::Span< const ProtoLiteral > Literals() const
const std::vector< ProtoLiteral > & TargetPhase() const
void SetLevelImplied(int level)
Asserts that the decision at level is implied by earlier decisions.
Contain the logic to decide when to restart a SAT tree search.
Definition restart.h:32
void CloseTree(ProtoTrail &path, int level)
SharedTreeManager(const SharedTreeManager &)=delete
void ProposeSplit(ProtoTrail &path, ProtoLiteral decision)
void ReplaceTree(ProtoTrail &path)
Assigns a path prefix that the worker should explore.
bool SyncTree(ProtoTrail &path) ABSL_LOCKS_EXCLUDED(mu_)
int NumNodes() const ABSL_LOCKS_EXCLUDED(mu_)
SharedTreeWorker & operator=(const SharedTreeWorker &)=delete
SharedTreeWorker(const SharedTreeWorker &)=delete
DecisionBuilder *const phase
The decision builder we are going to use in this dive.
IntVar * var
GRBmodel * model
int lit
int literal
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
STL namespace.
int nodes