Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
work_assignment.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 <array>
18#include <cmath>
19#include <deque>
20#include <functional>
21#include <limits>
22#include <memory>
23#include <optional>
24#include <string>
25#include <tuple>
26#include <utility>
27#include <vector>
28
29#include "absl/container/flat_hash_set.h"
30#include "absl/log/check.h"
31#include "absl/strings/str_cat.h"
32#include "absl/synchronization/mutex.h"
33#include "absl/types/span.h"
37#include "ortools/sat/integer.h"
39#include "ortools/sat/model.h"
40#include "ortools/sat/restart.h"
43#include "ortools/sat/sat_parameters.pb.h"
46#include "ortools/sat/util.h"
49
51namespace {
52
53// We restart the shared tree 10 times after 2 restarts per worker. After that
54// we restart when the tree reaches the maximum allowable number of nodes, but
55// still at most once per 2 restarts per worker.
56const int kSyncsPerWorkerPerRestart = 2;
57const int kNumInitialRestarts = 10;
58
59// If you build a tree by expanding the nodes with minimal depth+discrepancy,
60// the number of leaves when all nodes with a given value have been split
61// follows the fibonacci sequence:
62// num_leaves(0) := 2;
63// num_leaves(1) := 3;
64// num_leaves(n) := num_leaves(n-1) + num_leaves(n-2)
65// This function returns f(n) := min({i | num_leaves(i) >= n})
66int MaxAllowedDiscrepancyPlusDepth(int num_leaves) {
67 int i = 0;
68 int a = 1;
69 int b = 2;
70 while (b < num_leaves) {
71 std::tie(a, b) = std::make_pair(b, a + b);
72 ++i;
73 }
74 return i;
75}
76} // namespace
77
79 IntegerEncoder* encoder) const {
80 DCHECK_LT(proto_var_, mapping->NumProtoVariables());
81 if (mapping->IsBoolean(proto_var_)) {
82 return mapping->Literal(proto_var_);
83 }
84 return encoder->GetOrCreateAssociatedLiteral(DecodeInteger(mapping));
85}
86
87IntegerLiteral ProtoLiteral::DecodeInteger(CpModelMapping* mapping) const {
88 const int positive_var = PositiveRef(proto_var_);
89 if (!mapping->IsInteger(positive_var)) {
90 return IntegerLiteral();
91 }
92 if (proto_var_ < 0) {
93 return IntegerLiteral::LowerOrEqual(mapping->Integer(positive_var), -lb_);
94 }
95 return IntegerLiteral::GreaterOrEqual(mapping->Integer(positive_var), lb_);
96}
97
98std::optional<ProtoLiteral> ProtoLiteral::EncodeInteger(
99 IntegerLiteral literal, CpModelMapping* mapping) {
100 IntegerVariable positive_var = PositiveVariable(literal.var);
101 const int model_var =
102 mapping->GetProtoVariableFromIntegerVariable(positive_var);
103 if (model_var == -1) {
104 return std::nullopt;
105 }
106 ProtoLiteral result{
107 literal.var == positive_var ? model_var : NegatedRef(model_var),
108 literal.bound};
109 DCHECK_EQ(result.DecodeInteger(mapping), literal);
110 DCHECK_EQ(result.Negated().DecodeInteger(mapping), literal.Negated());
111 return result;
112}
113std::optional<ProtoLiteral> ProtoLiteral::Encode(Literal literal,
114 CpModelMapping* mapping,
115 IntegerEncoder* encoder) {
116 if (literal.Index() == kNoLiteralIndex) {
117 return std::nullopt;
118 }
119 int model_var =
120 mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
121 if (model_var != -1) {
122 CHECK(mapping->IsBoolean(model_var));
123 ProtoLiteral result{
124 literal.IsPositive() ? model_var : NegatedRef(model_var),
125 literal.IsPositive() ? 1 : 0};
126 DCHECK_EQ(result.Decode(mapping, encoder), literal);
127 DCHECK_EQ(result.Negated().Decode(mapping, encoder), literal.Negated());
128 return result;
129 }
130 for (auto int_lit : encoder->GetIntegerLiterals(literal)) {
131 auto result = EncodeInteger(int_lit, mapping);
132 if (result.has_value()) {
133 DCHECK_EQ(result->DecodeInteger(mapping), int_lit);
134 DCHECK_EQ(result->Negated().DecodeInteger(mapping), int_lit.Negated());
135 return result;
136 }
137 }
138 return std::nullopt;
139}
140
142 IntegerValue objective_lb, int node_id) {
143 CHECK_GT(node_id, 0);
144 decision_indexes_.push_back(literals_.size());
145 literals_.push_back(decision);
146 node_ids_.push_back(node_id);
147 implications_.push_back({});
148 if (!level_to_objective_lbs_.empty()) {
149 objective_lb = std::max(level_to_objective_lbs_.back(), objective_lb);
150 }
151 level_to_objective_lbs_.push_back(objective_lb);
152}
153
155 DCHECK_GE(level, 1);
156 DCHECK_LE(level, decision_indexes_.size());
157 DCHECK_LE(level, implications_.size());
158 SetObjectiveLb(level - 1, ObjectiveLb(level));
159 const ProtoLiteral decision = Decision(level);
160 implication_level_[decision] = level - 1;
161 // We don't store implications for level 0, so only move implications up to
162 // the parent if we are removing level 2 or greater.
163 if (level >= 2) {
164 MutableImplications(level - 1).push_back(decision);
165 }
166 for (const ProtoLiteral& implication : Implications(level)) {
167 implication_level_[implication] = level - 1;
168 if (level >= 2) {
169 MutableImplications(level - 1).push_back(implication);
170 }
171 }
172 // implications_[level-1] stores the implications for level, which are now
173 // stored in the parent's implications, so we can delete them.
174 implications_.erase(implications_.begin() + level - 1);
175 decision_indexes_.erase(decision_indexes_.begin() + level - 1);
176 level_to_objective_lbs_.erase(level_to_objective_lbs_.begin() + level - 1);
177}
178
180 decision_indexes_.clear();
181 literals_.clear();
182 level_to_objective_lbs_.clear();
183 node_ids_.clear();
184 target_phase_.clear();
185 implication_level_.clear();
186 implications_.clear();
187}
188
189void ProtoTrail::SetObjectiveLb(int level, IntegerValue objective_lb) {
190 if (level == 0) return;
191 level_to_objective_lbs_[level - 1] =
192 std::max(objective_lb, level_to_objective_lbs_[level - 1]);
193}
194
195absl::Span<const int> ProtoTrail::NodeIds(int level) const {
196 DCHECK_LE(level, decision_indexes_.size());
197 int start = level == 0 ? 0 : decision_indexes_[level - 1];
198 int end = level == decision_indexes_.size() ? node_ids_.size()
199 : decision_indexes_[level];
200 return absl::MakeSpan(node_ids_.data() + start, end - start);
201}
202
203absl::Span<const ProtoLiteral> ProtoTrail::Implications(int level) const {
204 if (level > implications_.size() || level <= 0) {
205 return absl::MakeSpan(literals_.data(), 0);
206 }
207 return absl::MakeSpan(implications_[level - 1]);
208}
209
211 : params_(*model->GetOrCreate<SatParameters>()),
212 num_workers_(std::max(1, params_.shared_tree_num_workers())),
213 shared_response_manager_(model->GetOrCreate<SharedResponseManager>()),
214 num_splits_wanted_(
215 num_workers_ * params_.shared_tree_open_leaves_per_worker() - 1),
216 max_nodes_(params_.shared_tree_max_nodes_per_worker() >=
217 std::numeric_limits<int>::max() / num_workers_
218 ? std::numeric_limits<int>::max()
219 : num_workers_ *
220 params_.shared_tree_max_nodes_per_worker()) {
221 // Create the root node with a fake literal.
222 nodes_.push_back(
223 {.literal = ProtoLiteral(),
224 .objective_lb = shared_response_manager_->GetInnerObjectiveLowerBound(),
225 .trail_info = std::make_unique<NodeTrailInfo>()});
226 unassigned_leaves_.reserve(num_workers_);
227 unassigned_leaves_.push_back(&nodes_.back());
228}
229
231 absl::MutexLock mutex_lock(&mu_);
232 return nodes_.size();
233}
234
236 absl::MutexLock mutex_lock(&mu_);
237 std::vector<std::pair<Node*, int>> nodes = GetAssignedNodes(path);
238 if (!IsValid(path)) {
239 path.Clear();
240 return false;
241 }
242 // We don't rely on these being empty, but we expect them to be.
243 DCHECK(to_close_.empty());
244 DCHECK(to_update_.empty());
245 int prev_level = -1;
246 for (const auto& [node, level] : nodes) {
247 if (level == prev_level) {
248 to_close_.push_back(GetSibling(node));
249 } else if (level > 0 && node->objective_lb < path.ObjectiveLb(level)) {
250 node->objective_lb = path.ObjectiveLb(level);
251 to_update_.push_back(node->parent);
252 }
253 if (level > 0 && !node->closed) {
254 NodeTrailInfo* trail_info = GetTrailInfo(node);
255 for (const ProtoLiteral& implication : path.Implications(level)) {
256 trail_info->implications.insert(implication);
257 }
258 }
259 prev_level = level;
260 }
261 ProcessNodeChanges();
262 if (nodes.back().first->closed) {
263 path.Clear();
264 return false;
265 }
266 // Restart after processing updates - we might learn a new objective bound.
267 if (++num_syncs_since_restart_ / num_workers_ > kSyncsPerWorkerPerRestart &&
268 num_restarts_ < kNumInitialRestarts) {
269 RestartLockHeld();
270 path.Clear();
271 return false;
272 }
273 // Sync lower bounds and implications from the shared tree to `path`.
274 AssignLeaf(path, nodes.back().first);
275 return true;
276}
277
279 absl::MutexLock mutex_lock(&mu_);
280 if (!IsValid(path)) return;
281 std::vector<std::pair<Node*, int>> nodes = GetAssignedNodes(path);
282 if (nodes.back().first->closed) {
283 VLOG(2) << "Cannot split closed node";
284 return;
285 }
286 if (nodes.back().first->children[0] != nullptr) {
287 LOG_IF(WARNING, nodes.size() > 1)
288 << "Cannot resplit previously split node @ " << nodes.back().second
289 << "/" << nodes.size();
290 return;
291 }
292 if (nodes_.size() + 2 > max_nodes_) {
293 VLOG(2) << "Too many nodes to accept split";
294 return;
295 }
296 if (num_splits_wanted_ <= 0) {
297 VLOG(2) << "Enough splits for now";
298 return;
299 }
300 const int num_desired_leaves =
301 params_.shared_tree_open_leaves_per_worker() * num_workers_;
302 if (params_.shared_tree_split_strategy() ==
303 SatParameters::SPLIT_STRATEGY_DISCREPANCY ||
304 params_.shared_tree_split_strategy() ==
305 SatParameters::SPLIT_STRATEGY_AUTO) {
306 int discrepancy = 0;
307 for (const auto& [node, level] : nodes) {
308 if (node->parent == nullptr || node->implied) continue;
309 IntegerValue sibling_bound = GetSibling(node)->objective_lb;
310 discrepancy += (node->objective_lb == sibling_bound
311 ? node != node->parent->children[0]
312 : node->objective_lb > sibling_bound);
313 }
314 // TODO(user): Need to write up the shape this creates.
315 // This rule will allow twice as many leaves in the preferred subtree.
316 if (discrepancy + path.MaxLevel() >
317 MaxAllowedDiscrepancyPlusDepth(num_desired_leaves)) {
318 VLOG(2) << "Too high discrepancy to accept split";
319 return;
320 }
321 } else if (params_.shared_tree_split_strategy() ==
322 SatParameters::SPLIT_STRATEGY_OBJECTIVE_LB) {
323 if (nodes.back().first->objective_lb > nodes.front().first->objective_lb) {
324 VLOG(2) << "Can only split nodes with minimum objective lb, "
325 << nodes.back().first->objective_lb << " > "
326 << nodes.front().first->objective_lb;
327 return;
328 }
329 } else if (params_.shared_tree_split_strategy() ==
330 SatParameters::SPLIT_STRATEGY_BALANCED_TREE) {
331 if (path.MaxLevel() + 1 > log2(num_desired_leaves)) {
332 VLOG(2) << "Tree too unbalanced to accept split";
333 return;
334 }
335 }
336 VLOG_EVERY_N(2, 10) << unassigned_leaves_.size() << " unassigned leaves, "
337 << nodes_.size() << " subtrees, " << num_splits_wanted_
338 << " splits wanted";
339 Split(nodes, decision);
340 auto [new_leaf, level] = nodes.back();
341 path.PushLevel(new_leaf->literal, new_leaf->objective_lb, new_leaf->id);
342}
343
345 absl::MutexLock mutex_lock(&mu_);
346 std::vector<std::pair<Node*, int>> nodes = GetAssignedNodes(path);
347 if (nodes.back().first->children[0] == nullptr &&
348 !nodes.back().first->closed && nodes.size() > 1) {
349 Node* leaf = nodes.back().first;
350 VLOG(2) << "Returning leaf to be replaced";
351 GetTrailInfo(leaf)->phase.assign(path.TargetPhase().begin(),
352 path.TargetPhase().end());
353 unassigned_leaves_.push_back(leaf);
354 }
355 path.Clear();
356 while (!unassigned_leaves_.empty()) {
357 const int i = num_leaves_assigned_++ % unassigned_leaves_.size();
358 std::swap(unassigned_leaves_[i], unassigned_leaves_.back());
359 Node* leaf = unassigned_leaves_.back();
360 unassigned_leaves_.pop_back();
361 if (!leaf->closed && leaf->children[0] == nullptr) {
362 AssignLeaf(path, leaf);
363 return;
364 }
365 }
366 VLOG(2) << "Assigning root because no unassigned leaves are available";
367 // TODO(user): Investigate assigning a random leaf so workers can still
368 // improve shared tree bounds.
369}
370
371SharedTreeManager::NodeTrailInfo* SharedTreeManager::GetTrailInfo(Node* node) {
372 CHECK(node != nullptr && !node->closed);
373 while (node->trail_info == nullptr) {
374 node = node->parent;
375 }
376 CHECK_NE(node, nullptr);
377 return node->trail_info.get();
378}
379
380SharedTreeManager::Node* SharedTreeManager::GetSibling(Node* node) {
381 if (node == nullptr || node->parent == nullptr) return nullptr;
382 if (node->parent->children[0] != node) {
383 return node->parent->children[0];
384 }
385 return node->parent->children[1];
386}
387
388void SharedTreeManager::Split(std::vector<std::pair<Node*, int>>& nodes,
389 ProtoLiteral lit) {
390 const auto [parent, level] = nodes.back();
391 DCHECK(parent->children[0] == nullptr);
392 DCHECK(parent->children[1] == nullptr);
393 parent->children[0] = MakeSubtree(parent, lit);
394 parent->children[1] = MakeSubtree(parent, lit.Negated());
395 NodeTrailInfo* trail_info = GetTrailInfo(parent);
396 if (trail_info != nullptr) {
397 parent->children[0]->trail_info = std::make_unique<NodeTrailInfo>(
398 NodeTrailInfo{.phase = trail_info->phase});
399 parent->children[1]->trail_info = std::make_unique<NodeTrailInfo>(
400 NodeTrailInfo{.phase = std::move(trail_info->phase)});
401 }
402 nodes.push_back(std::make_pair(parent->children[0], level + 1));
403 unassigned_leaves_.push_back(parent->children[1]);
404 --num_splits_wanted_;
405}
406
407SharedTreeManager::Node* SharedTreeManager::MakeSubtree(Node* parent,
408 ProtoLiteral literal) {
409 nodes_.push_back(
410 Node{.literal = literal,
411 .objective_lb = parent->objective_lb,
412 .parent = parent,
413 .id = static_cast<int>(nodes_.size() + node_id_offset_)});
414 return &nodes_.back();
415}
416
417void SharedTreeManager::ProcessNodeChanges() {
418 int num_newly_closed = 0;
419 while (!to_close_.empty()) {
420 Node* node = to_close_.back();
421 CHECK_NE(node, nullptr);
422 to_close_.pop_back();
423 // Iterate over open parents while each sibling is closed.
424 while (node != nullptr && !node->closed) {
425 ++num_newly_closed;
426 ++num_closed_nodes_;
427 node->closed = true;
428 // Keep the root trail_info so GetTrailInfo never returns nullptr.
429 if (node->parent != nullptr) node->trail_info.reset();
430 node->objective_lb = kMaxIntegerValue;
431 // If we are closing a leaf, try to maintain the same number of leaves;
432 num_splits_wanted_ += (node->children[0] == nullptr);
433 for (Node* child : node->children) {
434 if (child == nullptr || child->closed) continue;
435 to_close_.push_back(child);
436 }
437 Node* sibling = GetSibling(node);
438 if (sibling != nullptr) {
439 sibling->implied = true;
440 if (!sibling->closed) {
441 break;
442 }
443 }
444 node = node->parent;
445 }
446 DCHECK(node == nullptr || node->closed);
447 if (node == nullptr) {
448 shared_response_manager_->NotifyThatImprovingProblemIsInfeasible(
449 ShortStatus());
450 } else if (node->parent != nullptr) {
451 to_update_.push_back(node->parent);
452 }
453 }
454 if (num_newly_closed > 0) {
455 shared_response_manager_->LogMessageWithThrottling(
456 "Tree", absl::StrCat("nodes:", nodes_.size(), "/", max_nodes_,
457 " closed:", num_closed_nodes_,
458 " unassigned:", unassigned_leaves_.size(),
459 " restarts:", num_restarts_));
460 }
461 // TODO(user): We could do resolution here by moving implications that
462 // are true in each child to the parent.
463 bool root_updated = false;
464 while (!to_update_.empty()) {
465 Node* node = to_update_.back();
466 to_update_.pop_back();
467 // Iterate over parents while the lower bound can be improved.
468 while (node != nullptr && !node->closed) {
469 DCHECK(node->children[0] != nullptr);
470 DCHECK(node->children[1] != nullptr);
471 NodeTrailInfo* trail_info = GetTrailInfo(node);
472 for (Node* child : node->children) {
473 if (child->implied && child->trail_info != nullptr) {
474 trail_info->implications.merge(child->trail_info->implications);
475 child->trail_info.reset();
476 }
477 }
478 IntegerValue child_bound = std::min(node->children[0]->objective_lb,
479 node->children[1]->objective_lb);
480 if (child_bound <= node->objective_lb) break;
481 node->objective_lb = child_bound;
482 node = node->parent;
483 }
484 if (node == nullptr) root_updated = true;
485 }
486 if (root_updated) {
487 shared_response_manager_->UpdateInnerObjectiveBounds(
488 ShortStatus(), nodes_[0].objective_lb, kMaxIntegerValue);
489 }
490 // These are shared via SharedBoundsManager, don't duplicate here.
491 nodes_[0].trail_info->implications.clear();
492}
493
494std::vector<std::pair<SharedTreeManager::Node*, int>>
495SharedTreeManager::GetAssignedNodes(const ProtoTrail& path) {
496 std::vector<std::pair<Node*, int>> nodes({std::make_pair(&nodes_[0], 0)});
497 if (!IsValid(path)) {
498 // Restart has happened, nodes in this path are no longer valid, but the
499 // root is equivalent.
500 return nodes;
501 }
502 for (int i = 0; i <= path.MaxLevel(); ++i) {
503 for (int id : path.NodeIds(i)) {
504 const int index = id - node_id_offset_;
505 CHECK_GE(index, 0) << " in path.NodeIds(" << i
506 << "), max_level=" << path.MaxLevel();
507 CHECK_LT(index, nodes_.size());
508 DCHECK_EQ(nodes.back().first, nodes_[index].parent);
509 nodes.push_back(std::make_pair(&nodes_[index], i));
510 }
511 }
512 return nodes;
513}
514
516 absl::MutexLock mutex_lock(&mu_);
517 const int node_id_to_close = path.NodeIds(level).front();
518 path.Clear();
519 if (node_id_to_close < node_id_offset_) return;
520 Node* node = &nodes_[node_id_to_close - node_id_offset_];
521 VLOG(2) << "Closing subtree at level " << level;
522 DCHECK(to_close_.empty());
523 to_close_.push_back(node);
524 ProcessNodeChanges();
525}
526
527void SharedTreeManager::AssignLeaf(ProtoTrail& path, Node* leaf) {
528 path.Clear();
529 std::vector<Node*> reversed_path;
530 while (leaf != &nodes_[0]) {
531 reversed_path.push_back(&nodes_[leaf->id - node_id_offset_]);
532 leaf = leaf->parent;
533 }
534 while (!reversed_path.empty()) {
535 Node* leaf = reversed_path.back();
536 reversed_path.pop_back();
537 path.PushLevel(leaf->literal, leaf->objective_lb, leaf->id);
538 if (leaf->implied) {
539 path.SetLevelImplied(path.MaxLevel());
540 }
541 if (params_.shared_tree_worker_enable_trail_sharing()) {
542 for (const ProtoLiteral& implication : GetTrailInfo(leaf)->implications) {
543 path.AddImplication(path.MaxLevel(), implication);
544 }
545 }
546 }
547}
548
549bool SharedTreeManager::IsValid(const ProtoTrail& path) const {
550 auto node_ids = path.NodeIds(path.MaxLevel());
551 if (node_ids.empty()) return true;
552 if (node_ids.back() < node_id_offset_) return false;
553 return true;
554}
555
556void SharedTreeManager::RestartLockHeld() {
557 node_id_offset_ += nodes_.size();
558 nodes_.resize(1);
559 nodes_[0].id = node_id_offset_;
560 nodes_[0].children = {nullptr, nullptr};
561 unassigned_leaves_.clear();
562 num_splits_wanted_ =
563 num_workers_ * params_.shared_tree_open_leaves_per_worker() - 1;
564 num_closed_nodes_ = 0;
565 num_restarts_ += 1;
566 num_syncs_since_restart_ = 0;
567}
568
569std::string SharedTreeManager::ShortStatus() const {
570 return absl::StrCat("shared_tree_manager(r=", num_restarts_,
571 " n=", nodes_.size(), ")");
572}
573
575 : parameters_(model->GetOrCreate<SatParameters>()),
576 shared_response_(model->GetOrCreate<SharedResponseManager>()),
577 time_limit_(model->GetOrCreate<TimeLimit>()),
578 manager_(model->GetOrCreate<SharedTreeManager>()),
579 mapping_(model->GetOrCreate<CpModelMapping>()),
580 sat_solver_(model->GetOrCreate<SatSolver>()),
581 trail_(model->GetOrCreate<Trail>()),
582 integer_trail_(model->GetOrCreate<IntegerTrail>()),
583 encoder_(model->GetOrCreate<IntegerEncoder>()),
584 objective_(model->Get<ObjectiveDefinition>()),
585 random_(model->GetOrCreate<ModelRandomGenerator>()),
586 helper_(model->GetOrCreate<IntegerSearchHelper>()),
587 heuristics_(model->GetOrCreate<SearchHeuristics>()),
588 decision_policy_(model->GetOrCreate<SatDecisionPolicy>()),
589 restart_policy_(model->GetOrCreate<RestartPolicy>()),
590 level_zero_callbacks_(model->GetOrCreate<LevelZeroCallbackHelper>()),
591 reversible_int_repository_(model->GetOrCreate<RevIntRepository>()),
592 assigned_tree_lbds_(/*window_size=*/8) {}
593
594const std::vector<Literal>& SharedTreeWorker::DecisionReason(int level) {
595 CHECK_LE(level, assigned_tree_literals_.size());
596 reason_.clear();
597 for (int i = 0; i < level; ++i) {
598 reason_.push_back(assigned_tree_literals_[i].Negated());
599 }
600 return reason_;
601}
602
603bool SharedTreeWorker::AddDecisionImplication(Literal lit, int level) {
604 CHECK_NE(lit.Index(), kNoLiteralIndex);
605 CHECK(!sat_solver_->Assignment().LiteralIsTrue(lit));
606 if (sat_solver_->Assignment().LiteralIsFalse(lit)) {
607 VLOG(2) << "Closing subtree via impl at " << level + 1
608 << " assigned=" << assigned_tree_.MaxLevel();
609 integer_trail_->ReportConflict(DecisionReason(level), {});
610 manager_->CloseTree(assigned_tree_, level);
611 assigned_tree_literals_.clear();
612 return false;
613 }
614 integer_trail_->EnqueueLiteral(lit, DecisionReason(level), {});
615 VLOG(2) << "Learned shared clause";
616 return true;
617}
618
619bool SharedTreeWorker::AddImplications() {
620 const int level = sat_solver_->CurrentDecisionLevel();
621 // Level 0 implications are unit clauses and are synced elsewhere.
622 if (level == 0) return false;
623 if (level > assigned_tree_.MaxLevel()) {
624 return false;
625 }
626 rev_num_processed_implications_.resize(level + 1, 0);
627 auto& num_processed_implications = rev_num_processed_implications_[level];
628 reversible_int_repository_->SaveState(&num_processed_implications);
629 absl::Span<const Literal> implied_literals =
630 absl::MakeConstSpan(assigned_tree_implications_[level - 1])
631 .subspan(num_processed_implications);
632 bool added_clause = false;
633 for (Literal impl : implied_literals) {
634 ++num_processed_implications;
635 if (sat_solver_->Assignment().LiteralIsTrue(impl)) continue;
636 added_clause = true;
637 if (!AddDecisionImplication(impl, level)) return true;
638 }
639 if (objective_ != nullptr &&
640 objective_->objective_var != kNoIntegerVariable) {
641 const IntegerValue obj_lb =
642 integer_trail_->LowerBound(objective_->objective_var);
643 assigned_tree_.SetObjectiveLb(level, obj_lb);
644 const Literal obj_lit =
646 objective_->objective_var, assigned_tree_.ObjectiveLb(level)));
647 if (!sat_solver_->Assignment().LiteralIsTrue(obj_lit)) {
648 AddDecisionImplication(obj_lit, level);
649 return true;
650 }
651 }
652 return added_clause;
653}
654
655bool SharedTreeWorker::SyncWithLocalTrail() {
656 while (true) {
657 if (!sat_solver_->FinishPropagation()) return false;
658 // Ensure we are at fixed point w.r.t. implications in the tree up to the
659 // current level.
660 if (AddImplications()) continue;
661
662 if (!helper_->BeforeTakingDecision()) return false;
663 const int level = sat_solver_->CurrentDecisionLevel();
664 if (parameters_->shared_tree_worker_enable_trail_sharing() && level > 0 &&
665 level <= assigned_tree_.MaxLevel()) {
666 // Add implications from the local trail to share with other workers.
667 reversible_int_repository_->SaveState(&reversible_trail_index_);
668 for (int i = trail_->Index() - 1; i >= reversible_trail_index_; --i) {
669 const Literal lit = (*trail_)[i];
670 if (trail_->AssignmentType(lit.Variable()) ==
672 break;
673 }
674 std::optional<ProtoLiteral> encoded = EncodeDecision(lit);
675 if (!encoded.has_value()) continue;
676 assigned_tree_.AddImplication(level, *encoded);
677 }
678 reversible_trail_index_ = trail_->Index();
679 }
680 if (level >= assigned_tree_.MaxLevel()) break;
681 // The next decision is assigned, make sure it makes sense.
682 const Literal next_decision = assigned_tree_literals_[level];
683 if (!sat_solver_->Assignment().LiteralIsAssigned(next_decision)) break;
684 if (sat_solver_->Assignment().LiteralIsFalse(next_decision)) {
685 // Next assigned decision is impossible.
686 VLOG(2) << "Closing subtree at " << level + 1
687 << " assigned=" << assigned_tree_.MaxLevel();
688 manager_->CloseTree(assigned_tree_, level + 1);
689 assigned_tree_literals_.clear();
690 assigned_tree_implications_.clear();
691 sat_solver_->Backtrack(0);
692 } else {
693 // The next level is implied by the current one.
694 assigned_tree_.SetLevelImplied(level + 1);
695 if (level > 0) {
696 assigned_tree_implications_[level - 1].insert(
697 assigned_tree_implications_[level - 1].end(),
698 assigned_tree_implications_[level].begin(),
699 assigned_tree_implications_[level].end());
700 }
701 assigned_tree_implications_.erase(assigned_tree_implications_.begin() +
702 level);
703 assigned_tree_literals_.erase(assigned_tree_literals_.begin() + level);
704 }
705 }
706 return true;
707}
708
709bool SharedTreeWorker::NextDecision(LiteralIndex* decision_index) {
710 const auto& decision_policy =
711 heuristics_->decision_policies[heuristics_->policy_index];
712 const int next_level = sat_solver_->CurrentDecisionLevel() + 1;
713 new_split_available_ = next_level == assigned_tree_.MaxLevel() + 1;
714
715 CHECK_EQ(assigned_tree_literals_.size(), assigned_tree_.MaxLevel());
716 if (next_level <= assigned_tree_.MaxLevel()) {
717 VLOG(2) << "Following shared trail depth=" << next_level << " "
718 << parameters_->name();
719 const Literal decision = assigned_tree_literals_[next_level - 1];
720 CHECK(!sat_solver_->Assignment().LiteralIsFalse(decision))
721 << " at depth " << next_level << " " << parameters_->name();
722 CHECK(!sat_solver_->Assignment().LiteralIsTrue(decision));
723 *decision_index = decision.Index();
724 return true;
725 }
726 if (objective_ == nullptr ||
727 objective_->objective_var == kNoIntegerVariable) {
728 return helper_->GetDecision(decision_policy, decision_index);
729 }
730 // If the current node is close to the global lower bound, maybe try to
731 // improve it.
732 const IntegerValue root_obj_lb =
733 integer_trail_->LevelZeroLowerBound(objective_->objective_var);
734 const IntegerValue root_obj_ub =
735 integer_trail_->LevelZeroUpperBound(objective_->objective_var);
736 const IntegerValue obj_split =
737 root_obj_lb + absl::LogUniform<int64_t>(
738 *random_, 0, (root_obj_ub - root_obj_lb).value());
739 const double objective_split_probability =
740 parameters_->shared_tree_worker_objective_split_probability();
741 return helper_->GetDecision(
742 [&]() -> BooleanOrIntegerLiteral {
743 IntegerValue obj_lb =
744 integer_trail_->LowerBound(objective_->objective_var);
745 IntegerValue obj_ub =
746 integer_trail_->UpperBound(objective_->objective_var);
747 if (obj_lb > obj_split || obj_ub <= obj_split ||
748 next_level > assigned_tree_.MaxLevel() + 1 ||
749 absl::Bernoulli(*random_, 1 - objective_split_probability)) {
750 return decision_policy();
751 }
752 return BooleanOrIntegerLiteral(
753 IntegerLiteral::LowerOrEqual(objective_->objective_var, obj_split));
754 },
755 decision_index);
756}
757
758void SharedTreeWorker::MaybeProposeSplit() {
759 if (!new_split_available_ ||
760 sat_solver_->CurrentDecisionLevel() != assigned_tree_.MaxLevel() + 1) {
761 return;
762 }
763 new_split_available_ = false;
764 const Literal split_decision =
765 sat_solver_->Decisions()[assigned_tree_.MaxLevel()].literal;
766 const std::optional<ProtoLiteral> encoded = EncodeDecision(split_decision);
767 if (encoded.has_value()) {
768 CHECK_EQ(assigned_tree_literals_.size(), assigned_tree_.MaxLevel());
769 manager_->ProposeSplit(assigned_tree_, *encoded);
770 if (assigned_tree_.MaxLevel() > assigned_tree_literals_.size()) {
771 assigned_tree_literals_.push_back(split_decision);
772 assigned_tree_implications_.push_back({});
773 }
774 CHECK_EQ(assigned_tree_literals_.size(), assigned_tree_.MaxLevel());
775 }
776}
777
778bool SharedTreeWorker::ShouldReplaceSubtree() {
779 // If we have no assignment, try to get one.
780 if (assigned_tree_.MaxLevel() == 0) return true;
781 if (restart_policy_->NumRestarts() <
782 parameters_->shared_tree_worker_min_restarts_per_subtree()) {
783 return false;
784 }
785 return assigned_tree_lbds_.WindowAverage() <
786 restart_policy_->LbdAverageSinceReset();
787}
788
789bool SharedTreeWorker::SyncWithSharedTree() {
790 manager_->SyncTree(assigned_tree_);
791 if (ShouldReplaceSubtree()) {
792 ++num_trees_;
793 VLOG(2) << parameters_->name() << " acquiring tree #" << num_trees_
794 << " after " << num_restarts_ - tree_assignment_restart_
795 << " restarts prev depth: " << assigned_tree_.MaxLevel()
796 << " target: " << assigned_tree_lbds_.WindowAverage()
797 << " lbd: " << restart_policy_->LbdAverageSinceReset();
798 if (parameters_->shared_tree_worker_enable_trail_sharing()) {
799 std::vector<ProtoLiteral> phase_out;
800 for (Literal lit : decision_policy_->GetBestPartialAssignment()) {
801 auto encoded = ProtoLiteral::Encode(lit, mapping_, encoder_);
802 if (!encoded.has_value()) continue;
803 phase_out.push_back(*encoded);
804 }
805 assigned_tree_.SetPhase(phase_out);
806 }
807 manager_->ReplaceTree(assigned_tree_);
808 tree_assignment_restart_ = num_restarts_;
809 assigned_tree_lbds_.Add(restart_policy_->LbdAverageSinceReset());
810 restart_policy_->Reset();
811 if (parameters_->shared_tree_worker_enable_trail_sharing()) {
812 decision_policy_->ClearBestPartialAssignment();
813 for (const ProtoLiteral& lit : assigned_tree_.TargetPhase()) {
814 decision_policy_->SetTargetPolarity(DecodeDecision(lit));
815 }
816 }
817 }
818 VLOG(2) << "Assigned level: " << assigned_tree_.MaxLevel() << " "
819 << parameters_->name();
820 assigned_tree_literals_.clear();
821 assigned_tree_implications_.clear();
822 for (int i = 1; i <= assigned_tree_.MaxLevel(); ++i) {
823 assigned_tree_literals_.push_back(
824 DecodeDecision(assigned_tree_.Decision(i)));
825 std::vector<Literal> implications;
826 for (const ProtoLiteral& impl : assigned_tree_.Implications(i)) {
827 implications.push_back(DecodeDecision(impl));
828 }
829 assigned_tree_implications_.push_back(std::move(implications));
830 }
831 return true;
832}
833
835 const std::function<void()>& feasible_solution_observer) {
836 // Inside GetAssociatedLiteral if a literal becomes fixed at level 0 during
837 // Search,the code checks it is at level 0 when decoding the literal, but
838 // the fixed literals are cached, so we can create them now to avoid a
839 // crash.
840 sat_solver_->Backtrack(0);
841 encoder_->GetTrueLiteral();
842 encoder_->GetFalseLiteral();
843 level_zero_callbacks_->callbacks.push_back(
844 [this]() { return SyncWithSharedTree(); });
845 const bool has_objective =
846 objective_ != nullptr && objective_->objective_var != kNoIntegerVariable;
847 while (!time_limit_->LimitReached()) {
848 if (!sat_solver_->FinishPropagation()) {
849 return sat_solver_->UnsatStatus();
850 }
851 if (heuristics_->restart_policies[heuristics_->policy_index]()) {
852 ++num_restarts_;
853 heuristics_->policy_index =
854 num_restarts_ % heuristics_->decision_policies.size();
855 sat_solver_->Backtrack(0);
856 }
857 if (!SyncWithLocalTrail()) return sat_solver_->UnsatStatus();
858 LiteralIndex decision_index;
859 if (!NextDecision(&decision_index)) continue;
860 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
861 if (decision_index == kNoLiteralIndex) {
862 feasible_solution_observer();
863 if (!has_objective) return SatSolver::FEASIBLE;
864 const IntegerValue objective =
865 integer_trail_->LowerBound(objective_->objective_var);
866 sat_solver_->Backtrack(0);
867 if (!integer_trail_->Enqueue(
869 objective - 1),
870 {}, {})) {
872 }
873
874 continue;
875 }
876 const Literal decision(decision_index);
877 CHECK(!sat_solver_->Assignment().LiteralIsFalse(decision));
878 CHECK(!sat_solver_->Assignment().LiteralIsTrue(decision));
879 if (!helper_->TakeDecision(decision)) {
880 return sat_solver_->UnsatStatus();
881 }
882 MaybeProposeSplit();
883 }
884
886}
887
888Literal SharedTreeWorker::DecodeDecision(ProtoLiteral lit) {
889 return lit.Decode(mapping_, encoder_);
890}
891
892std::optional<ProtoLiteral> SharedTreeWorker::EncodeDecision(Literal decision) {
893 return ProtoLiteral::Encode(decision, mapping_, encoder_);
894}
895
896} // namespace operations_research::sat
int64_t max
void SaveState(T *object)
Definition rev.h:62
void Add(int value)
Adds the next integer of the stream.
int GetProtoVariableFromBooleanVariable(BooleanVariable var) const
int NumProtoVariables() const
Returns the number of variables in the loaded proto.
IntegerVariable Integer(int ref) const
Literal GetTrueLiteral()
Gets the literal always set to true, make it if it does not exist.
Definition integer.h:641
const InlinedIntegerLiteralVector & GetIntegerLiterals(Literal lit) const
Returns the IntegerLiterals that were associated with the given Literal.
Definition integer.h:584
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
Definition integer.cc:273
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
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.h:1076
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1817
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1392
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
static std::optional< ProtoLiteral > Encode(Literal, CpModelMapping *, IntegerEncoder *)
Literal Decode(CpModelMapping *, IntegerEncoder *) const
Note you should only decode integer literals at the root level.
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.
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
int NumRestarts() const
Returns the number of restarts since the last Reset().
Definition restart.h:55
void Reset()
Resets the policy using the current model parameters.
Definition restart.cc:30
void SetTargetPolarity(Literal l)
Like SetAssignmentPreference() but it can be overridden by phase-saving.
absl::Span< const Literal > GetBestPartialAssignment() const
void Backtrack(int target_level)
const VariablesAssignment & Assignment() const
Definition sat_solver.h:388
const std::vector< Decision > & Decisions() const
Definition sat_solver.h:385
ABSL_MUST_USE_RESULT bool FinishPropagation()
void LogMessageWithThrottling(const std::string &prefix, const std::string &message)
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
Updates the inner objective bounds.
void CloseTree(ProtoTrail &path, int level)
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_)
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
int AssignmentType(BooleanVariable var) const
Definition sat_base.h:671
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
int64_t a
Definition table.cc:44
int64_t value
GRBmodel * model
int lit
int literal
int index
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
const LiteralIndex kNoLiteralIndex(-1)
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition integer.h:193
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
STL namespace.
int model_var
Definition rins.cc:99
int nodes
std::optional< int64_t > end
int64_t start
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1667
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1673
std::vector< std::function< bool()> > callbacks
std::vector< std::function< bool()> > restart_policies
std::vector< std::function< BooleanOrIntegerLiteral()> > decision_policies
int policy_index
Index in the vectors above that indicate the current configuration.