Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
synchronization.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <sys/types.h>
17
18#include <algorithm>
19#include <atomic>
20#include <cctype>
21#include <cmath>
22#include <cstddef>
23#include <cstdint>
24#include <ctime>
25#include <deque>
26#include <functional>
27#include <limits>
28#include <memory>
29#include <string>
30#include <tuple>
31#include <utility>
32#include <vector>
33
34#include "absl/algorithm/container.h"
36#include "ortools/base/timer.h"
38#if !defined(__PORTABLE_PLATFORM__)
41#endif // __PORTABLE_PLATFORM__
42#include "absl/base/thread_annotations.h"
43#include "absl/container/btree_map.h"
44#include "absl/container/flat_hash_map.h"
45#include "absl/container/flat_hash_set.h"
46#include "absl/flags/flag.h"
47#include "absl/hash/hash.h"
48#include "absl/log/check.h"
49#include "absl/log/log.h"
50#include "absl/numeric/int128.h"
51#include "absl/random/bit_gen_ref.h"
52#include "absl/random/distributions.h"
53#include "absl/strings/str_cat.h"
54#include "absl/strings/str_format.h"
55#include "absl/strings/string_view.h"
56#include "absl/synchronization/mutex.h"
57#include "absl/types/span.h"
62#include "ortools/sat/model.h"
65#include "ortools/sat/util.h"
66#include "ortools/util/bitset.h"
70
71ABSL_FLAG(bool, cp_model_dump_solutions, false,
72 "DEBUG ONLY. If true, all the intermediate solution will be dumped "
73 "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.");
74ABSL_FLAG(bool, cp_model_dump_tightened_models, false,
75 "DEBUG ONLY. If true, dump tightened models incoporating all bounds "
76 "changes under '\"FLAGS_cp_model_dump_prefix\" + "
77 "\"tight_model_xxx.pb.txt\"'.");
78
79namespace operations_research {
80namespace sat {
81
82std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
84 // Only add to the alternative path if it has the correct source id.
85 if (alternative_path_.num_solutions_to_keep() > 0 &&
86 solution.source_id == alternative_path_.source_id()) {
87 alternative_path_.Add(solution);
88 if (solution.rank < best_solutions_.GetBestRank()) {
89 VLOG(2) << "ALTERNATIVE WIN !";
90 }
91 }
92
93 // For now we only return a solution if it was stored in best_solutions_.
94 return best_solutions_.Add(std::move(solution));
95}
96
97void SharedSolutionPool::Synchronize(absl::BitGenRef random) {
98 // Update the "seeds" for the aternative path.
99 if (alternative_path_.num_solutions_to_keep() > 0) {
100 absl::MutexLock mutex_lock(mutex_);
101
102 auto process_solution =
104 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
105 if (solution.variable_values.empty()) return;
106 if (solution.rank < min_rank_ || solution.rank > max_rank_) {
107 // Recompute buckets.
108 min_rank_ = std::min(min_rank_, solution.rank);
109 max_rank_ = std::max(max_rank_, solution.rank);
110
111 // We want to store around 100 MB max.
112 int num_solutions = std::max<int>(
113 10, 100'000'000 / solution.variable_values.size());
114 const int64_t range = max_rank_ - min_rank_ + 1;
115 if (num_solutions > range) {
116 num_solutions = range;
117 }
118
119 // But if the number of variables is low, we do not want
120 // to use a lot of space/time just iterating over num_solutions.
121 //
122 // TODO(user): Rework the algo to be in
123 // O(num_different_solutions) rather than initializing the
124 // maximum amount right away.
125 num_solutions = std::min(num_solutions, 1'000);
126
127 // Resize and recompute rank_.
128 //
129 // seeds_[i] should contains solution in [ranks_[i],
130 // rank_[i+1]). rank_[0] is always min_rank_. As long as we have
131 // room, we should have exactly one bucket per rank.
132 ranks_.resize(num_solutions);
133 seeds_.resize(num_solutions);
134
135 int64_t offset = (max_rank_ - min_rank_ + 1) / num_solutions;
136 CHECK_GT(offset, 0);
137 for (int i = 0; i < num_solutions; ++i) {
138 ranks_[i] = min_rank_ +
139 static_cast<int64_t>(absl::int128(i) *
140 absl::int128(range) /
141 absl::int128(num_solutions));
142 }
143
144 // Move existing solutions to their new bucket.
145 int to_index = seeds_.size() - 1;
146 for (int i = seeds_.size(); --i >= 0;) {
147 if (seeds_[i] == nullptr) continue;
148 while (to_index >= 0 && ranks_[to_index] > seeds_[i]->rank) {
149 --to_index;
150 }
151 seeds_[to_index] = std::move(seeds_[i]);
152 }
153 }
154
155 // rank[limit] is the first > solution.rank.
156 const int limit = std::upper_bound(ranks_.begin(), ranks_.end(),
157 solution.rank) -
158 ranks_.begin();
159 CHECK_GT(limit, 0);
160 seeds_[limit - 1] =
161 std::make_shared<SharedSolutionRepository<int64_t>::Solution>(
162 solution);
163 };
164
165 // All solution go through best_solutions_.Add(), so we only need
166 // to process these here.
167 best_solutions_.Synchronize(process_solution);
168 } else {
169 best_solutions_.Synchronize();
170 }
171 alternative_path_.Synchronize();
172
173 // If we try to improve the alternate path without success, reset it
174 // from a random path_seeds_.
175 //
176 // TODO(user): find a way to generate random solution and update the seeds
177 // with them. Shall we do that in a continuous way or only when needed?
178 if (alternative_path_.num_solutions_to_keep() > 0) {
179 // Restart the alternative path ?
180 const int threshold = std::max(
181 100, static_cast<int>(std::sqrt(best_solutions_.num_queried())));
182 if (alternative_path_.NumRecentlyNonImproving() > threshold) {
183 VLOG(2) << "Done. num_non_improving: "
184 << alternative_path_.NumRecentlyNonImproving()
185 << " achieved: " << alternative_path_.GetBestRank() << " / "
186 << best_solutions_.GetBestRank();
187 alternative_path_.ClearSolutionsAndIncreaseSourceId();
188 }
189
190 // If we restarted, or we are at the beginning, pick a seed for the path.
191 if (alternative_path_.NumSolutions() == 0) {
192 absl::MutexLock mutex_lock(mutex_);
193
194 // Pick random bucket with bias. If the bucket is empty, we will scan
195 // "worse" bucket until we find a solution. We never pick bucket 0.
196 if (seeds_.size() > 1) {
197 // Note that LogUniform() is always inclusive.
198 // TODO(user): Shall we bias even more?
199 int index = 1 + absl::LogUniform<int>(random, 0, seeds_.size() - 2);
200 for (; index < seeds_.size(); ++index) {
201 if (seeds_[index] != nullptr) {
202 alternative_path_.Add(*seeds_[index]);
203 alternative_path_.Synchronize();
204 VLOG(2) << "RESTART bucket=" << index << "/" << seeds_.size()
205 << " rank=" << alternative_path_.GetSolution(0)->rank
206 << " from_optimal="
207 << alternative_path_.GetSolution(0)->rank - min_rank_;
208 break;
209 }
210 }
211
212 // The last bucket should never be empty.
213 CHECK(seeds_.back() != nullptr);
214 CHECK_LT(index, seeds_.size());
215 }
216 }
217 }
218}
219
221 std::vector<double> lp_solution) {
222 if (lp_solution.empty()) return;
223
224 // Add this solution to the pool.
225 auto solution =
226 std::make_shared<SharedSolutionRepository<double>::Solution>();
227 solution->variable_values = std::move(lp_solution);
228
229 // We always prefer to keep the solution from the last synchronize batch.
230 {
231 absl::MutexLock mutex_lock(mutex_);
232 solution->rank = -num_synchronization_;
233 ++num_added_;
234 new_solutions_.push_back(solution);
235 }
236}
237
239 const std::vector<double>& lp_solution) {
240 absl::MutexLock mutex_lock(mutex_);
241 ++num_added_;
242 solutions_.push_back(lp_solution);
243 if (solutions_.size() > 100) solutions_.pop_front();
244}
245
247 absl::MutexLock mutex_lock(mutex_);
248 return !solutions_.empty();
249}
250
252 absl::MutexLock mutex_lock(mutex_);
253 if (solutions_.empty()) return {};
254
255 ++num_queried_;
256 std::vector<double> solution = std::move(solutions_.back());
257 solutions_.pop_back();
258 return solution;
259}
260
262 : parameters_(*model->GetOrCreate<SatParameters>()),
263 wall_timer_(*model->GetOrCreate<WallTimer>()),
264 shared_time_limit_(model->GetOrCreate<ModelSharedTimeLimit>()),
265 random_(model->GetOrCreate<ModelRandomGenerator>()),
266 solution_pool_(parameters_),
267 logger_(model->GetOrCreate<SolverLogger>()) {
268 bounds_logging_id_ = logger_->GetNewThrottledId();
269}
270
272 Model* model, CpSolverResponse* response) {
273 if (model == nullptr) return;
274 absl::MutexLock mutex_lock(mutex_);
275 for (const auto& set_stats : statistics_postprocessors_) {
276 set_stats(model, response);
277 }
278}
279
280void SharedResponseManager::LogMessage(absl::string_view prefix,
281 absl::string_view message) {
282 absl::MutexLock mutex_lock(mutex_);
283 SOLVER_LOG(logger_, absl::StrFormat("#%-5s %6.2fs %s", prefix,
284 wall_timer_.Get(), message));
285}
286
288 absl::string_view prefix, absl::string_view message) {
289 absl::MutexLock mutex_lock(mutex_);
290
291 int id;
292 auto it = throttling_ids_.find(prefix);
293 if (it == throttling_ids_.end()) {
294 id = throttling_ids_[prefix] = logger_->GetNewThrottledId();
295 } else {
296 id = it->second;
297 }
298 logger_->ThrottledLog(id, absl::StrFormat("#%-5s %6.2fs %s", prefix,
299 wall_timer_.Get(), message));
300}
301
303 absl::MutexLock mutex_lock(mutex_);
304
305 return logger_->LoggingIsEnabled();
306}
307
309 if (cp_model.has_objective()) {
310 objective_or_null_ = &cp_model.objective();
311 const Domain domain = ReadDomainFromProto(cp_model.objective());
312 if (!domain.IsEmpty()) {
313 UpdateInnerObjectiveBounds("initial_domain", IntegerValue(domain.Min()),
314 IntegerValue(domain.Max()));
315 }
316 } else {
317 objective_or_null_ = nullptr;
318 }
319}
320
322 absl::MutexLock mutex_lock(mutex_);
323 always_synchronize_ = always_synchronize;
324}
325
327 absl::MutexLock mutex_lock(mutex_);
328 update_integral_on_each_change_ = set;
329}
330
332 absl::MutexLock mutex_lock(mutex_);
333 UpdateGapIntegralInternal();
334}
335
336void SharedResponseManager::UpdateGapIntegralInternal() {
337 if (objective_or_null_ == nullptr) return;
338
339 const double current_time = shared_time_limit_->GetElapsedDeterministicTime();
340 const double time_delta = current_time - last_gap_integral_time_stamp_;
341
342 // We use the log of the absolute objective gap.
343 //
344 // Using the log should count no solution as just log(2*64) = 18, and
345 // otherwise just compare order of magnitude which seems nice. Also, It is
346 // more easy to compare the primal integral with the total time.
347 const CpObjectiveProto& obj = *objective_or_null_;
348 const double factor =
349 obj.scaling_factor() != 0.0 ? std::abs(obj.scaling_factor()) : 1.0;
350 const double bounds_delta = std::log(1 + factor * last_absolute_gap_);
351 gap_integral_ += time_delta * bounds_delta;
352
353 // Update with new value.
354 last_gap_integral_time_stamp_ = current_time;
355 last_absolute_gap_ =
356 std::max(0.0, static_cast<double>(inner_objective_upper_bound_) -
357 static_cast<double>(inner_objective_lower_bound_));
358}
359
361 const SatParameters& parameters) {
362 absl::MutexLock mutex_lock(mutex_);
363 if (objective_or_null_ == nullptr) return;
364 absolute_gap_limit_ = parameters.absolute_gap_limit();
365 relative_gap_limit_ = parameters.relative_gap_limit();
366}
367
368void SharedResponseManager::TestGapLimitsIfNeeded() {
369 // This is called on each internal limit change, so it is a good place to
370 // update the integral. Note that this is not called at the end of the search
371 // though.
372 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
373
374 // Abort if there is not limit set, if the gap is not defined or if we already
375 // proved optimality or infeasibility.
376 if (absolute_gap_limit_ == 0 && relative_gap_limit_ == 0) return;
377 if (best_solution_objective_value_ >= kMaxIntegerValue) return;
378 if (inner_objective_lower_bound_ <= kMinIntegerValue) return;
379 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) return;
380
381 const CpObjectiveProto& obj = *objective_or_null_;
382 const double user_best =
383 ScaleObjectiveValue(obj, best_solution_objective_value_);
384 const double user_bound =
385 ScaleObjectiveValue(obj, inner_objective_lower_bound_);
386 const double gap = std::abs(user_best - user_bound);
387 if (gap <= absolute_gap_limit_) {
388 SOLVER_LOG(logger_, "Absolute gap limit of ", absolute_gap_limit_,
389 " reached.");
390 UpdateBestStatus(CpSolverStatus::OPTIMAL);
391
392 // Note(user): Some code path in single-thread assumes that the problem
393 // can only be solved when they have proven infeasibility and do not check
394 // the ProblemIsSolved() method. So we force a stop here.
395 if (always_synchronize_) shared_time_limit_->Stop();
396 }
397 if (gap / std::max(1.0, std::abs(user_best)) < relative_gap_limit_) {
398 SOLVER_LOG(logger_, "Relative gap limit of ", relative_gap_limit_,
399 " reached.");
400 UpdateBestStatus(CpSolverStatus::OPTIMAL);
401
402 // Same as above.
403 if (always_synchronize_) shared_time_limit_->Stop();
404 }
405}
406
408 const std::string& update_info, IntegerValue lb, IntegerValue ub) {
409 absl::MutexLock mutex_lock(mutex_);
410 CHECK(objective_or_null_ != nullptr);
411
412 // The problem is already solved!
413 //
414 // TODO(user): A thread might not be notified right away that the new bounds
415 // that it is pushing make the problem infeasible. Fix that. For now we just
416 // abort early here to avoid logging the "#Done" message multiple times.
417 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
418 return;
419 }
420
421 const bool ub_change = ub < inner_objective_upper_bound_;
422 const bool lb_change = lb > inner_objective_lower_bound_;
423 if (!lb_change && !ub_change) return;
424
425 if (lb_change) {
426 // When the improving problem is infeasible, it is possible to report
427 // arbitrary high inner_objective_lower_bound_. We make sure it never cross
428 // the current best solution, so that we always report globally valid lower
429 // bound.
430 DCHECK_LE(inner_objective_upper_bound_, best_solution_objective_value_);
431 inner_objective_lower_bound_ =
432 std::min(best_solution_objective_value_, lb.value());
433 }
434 if (ub_change) {
435 inner_objective_upper_bound_ = ub.value();
436 }
437
438 if (always_synchronize_) {
439 synchronized_inner_objective_lower_bound_ =
440 IntegerValue(inner_objective_lower_bound_);
441 synchronized_inner_objective_upper_bound_ =
442 IntegerValue(inner_objective_upper_bound_);
443 }
444
445 if (!status_change_callbacks_.empty()) {
446 SolverStatusChangeInfo info = GetSolverStatusChangeInfo();
447 info.change_info = update_info;
448 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
449 info.solved = true;
450 } else {
451 info.new_lower_bound = lb_change;
452 info.new_upper_bound = ub_change;
453 }
454 for (const auto& callback : status_change_callbacks_) {
455 callback(info);
456 }
457 }
458
459 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
460 if (best_status_ == CpSolverStatus::FEASIBLE ||
461 best_status_ == CpSolverStatus::OPTIMAL) {
462 UpdateBestStatus(CpSolverStatus::OPTIMAL);
463 } else {
464 UpdateBestStatus(CpSolverStatus::INFEASIBLE);
465 }
466 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
467 return;
468 }
469 if (!best_bound_callbacks_.empty()) {
470 if (lb_change) {
471 const CpObjectiveProto& obj = *objective_or_null_;
472 double new_lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
473 for (const auto& callback_entry : best_bound_callbacks_) {
474 callback_entry.second(new_lb);
475 }
476 }
477 }
478 TestGapLimitsIfNeeded();
479}
480
481// Invariant: the status always start at UNKNOWN and can only evolve as follow:
482// UNKNOWN -> FEASIBLE -> OPTIMAL
483// UNKNOWN -> INFEASIBLE
485 absl::string_view worker_info) {
486 absl::MutexLock mutex_lock(mutex_);
487 if (best_status_ == CpSolverStatus::FEASIBLE ||
488 best_status_ == CpSolverStatus::OPTIMAL) {
489 // We also use this status to indicate that we enumerated all solutions to
490 // a feasible problem.
491 UpdateBestStatus(CpSolverStatus::OPTIMAL);
492
493 // We just proved that the best solution cannot be improved upon, so we
494 // have a new lower bound.
495 inner_objective_lower_bound_ = best_solution_objective_value_;
496 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
497 } else {
498 CHECK_EQ(num_solutions_, 0);
499 UpdateBestStatus(CpSolverStatus::INFEASIBLE);
500 }
501 if (!status_change_callbacks_.empty()) {
502 SolverStatusChangeInfo info = GetSolverStatusChangeInfo();
503 info.change_info = worker_info;
504 info.solved = true;
505 for (const auto& callback : status_change_callbacks_) {
506 callback(info);
507 }
508 }
509}
510
511void SharedResponseManager::AddUnsatCore(const std::vector<int>& core) {
512 absl::MutexLock mutex_lock(mutex_);
513 unsat_cores_ = core;
514}
515
517 absl::MutexLock mutex_lock(mutex_);
518 return synchronized_inner_objective_lower_bound_;
519}
520
522 absl::MutexLock mutex_lock(mutex_);
523 return synchronized_inner_objective_upper_bound_;
524}
525
527 solution_pool_.Synchronize(*random_);
528
529 absl::MutexLock mutex_lock(mutex_);
530 synchronized_inner_objective_lower_bound_ =
531 IntegerValue(inner_objective_lower_bound_);
532 synchronized_inner_objective_upper_bound_ =
533 IntegerValue(inner_objective_upper_bound_);
534 synchronized_best_status_ = best_status_;
535 if (solution_pool_.BestSolutions().NumSolutions() > 0) {
536 first_solution_solvers_should_stop_ = true;
537 }
538 logger_->FlushPendingThrottledLogs();
539}
540
542 absl::MutexLock mutex_lock(mutex_);
543 return IntegerValue(best_solution_objective_value_);
544}
545
547 absl::MutexLock mutex_lock(mutex_);
548 return gap_integral_;
549}
550
552 std::function<void(std::vector<int64_t>*)> postprocessor) {
553 absl::MutexLock mutex_lock(mutex_);
554 solution_postprocessors_.push_back(postprocessor);
555}
556
558 std::function<void(CpSolverResponse*)> postprocessor) {
559 absl::MutexLock mutex_lock(mutex_);
560 postprocessors_.push_back(postprocessor);
561}
562
564 std::function<void(CpSolverResponse*)> postprocessor) {
565 absl::MutexLock mutex_lock(mutex_);
566 final_postprocessors_.push_back(postprocessor);
567}
568
570 std::function<void(Model*, CpSolverResponse*)> postprocessor) {
571 absl::MutexLock mutex_lock(mutex_);
572 statistics_postprocessors_.push_back(postprocessor);
573}
574
576 std::function<void(const CpSolverResponse&)> callback) {
577 absl::MutexLock mutex_lock(mutex_);
578 const int id = next_callback_id_++;
579 callbacks_.emplace_back(id, std::move(callback));
580 return id;
581}
582
584 absl::MutexLock mutex_lock(mutex_);
585 for (int i = 0; i < callbacks_.size(); ++i) {
586 if (callbacks_[i].first == callback_id) {
587 callbacks_.erase(callbacks_.begin() + i);
588 return;
589 }
590 }
591 LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
592}
593
595 std::function<std::string(const CpSolverResponse&)> callback) {
596 absl::MutexLock mutex_lock(mutex_);
597 const int id = next_search_log_callback_id_++;
598 search_log_callbacks_.emplace_back(id, std::move(callback));
599 return id;
600}
601
603 std::function<void(const SolverStatusChangeInfo&)> callback) {
604 absl::MutexLock mutex_lock(mutex_);
605 status_change_callbacks_.push_back(std::move(callback));
606}
607
609 absl::MutexLock mutex_lock(mutex_);
610 for (int i = 0; i < search_log_callbacks_.size(); ++i) {
611 if (search_log_callbacks_[i].first == callback_id) {
612 search_log_callbacks_.erase(search_log_callbacks_.begin() + i);
613 return;
614 }
615 }
616 LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
617}
618
620 std::function<void(double)> callback) {
621 absl::MutexLock mutex_lock(mutex_);
622 const int id = next_best_bound_callback_id_++;
623 best_bound_callbacks_.emplace_back(id, std::move(callback));
624 return id;
625}
626
628 absl::MutexLock mutex_lock(mutex_);
629 for (int i = 0; i < best_bound_callbacks_.size(); ++i) {
630 if (best_bound_callbacks_[i].first == callback_id) {
631 best_bound_callbacks_.erase(best_bound_callbacks_.begin() + i);
632 return;
633 }
634 }
635 LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
636}
637
638CpSolverResponse SharedResponseManager::GetResponseInternal(
639 absl::Span<const int64_t> variable_values,
640 absl::string_view solution_info) {
641 CpSolverResponse result;
642 result.set_status(best_status_);
643 if (!unsat_cores_.empty()) {
644 DCHECK_EQ(best_status_, CpSolverStatus::INFEASIBLE);
646 unsat_cores_.begin(), unsat_cores_.end());
647 }
648 FillObjectiveValuesInResponse(&result);
649 result.set_solution_info(solution_info);
650
651 // Tricky: We copy the solution now for the case where MergeFrom() belows
652 // override it!
653 //
654 // TODO(user): Fix. This is messy, we should really just override stats not
655 // important things like solution or status with the MergeFrom() below.
656 if (best_status_ == CpSolverStatus::FEASIBLE ||
657 best_status_ == CpSolverStatus::OPTIMAL) {
658 result.mutable_solution()->Assign(variable_values.begin(),
659 variable_values.end());
660 }
661
662 // Note that we allow subsolver_responses_ to override the fields set above.
663 // That is the status, solution_info and objective values...
664 if (!subsolver_responses_.empty()) {
665 result.MergeFrom(subsolver_responses_.front());
666 }
667
668 if (result.status() == CpSolverStatus::FEASIBLE ||
669 result.status() == CpSolverStatus::OPTIMAL) {
670 // We need to copy the solution before we postsolve it.
671 std::vector<int64_t> solution(result.solution().begin(),
672 result.solution().end());
673 for (int i = solution_postprocessors_.size(); --i >= 0;) {
674 solution_postprocessors_[i](&solution);
675 }
676 result.mutable_solution()->Assign(solution.begin(), solution.end());
677 }
678
679 // Apply response postprocessor to set things like timing information.
680 for (int i = postprocessors_.size(); --i >= 0;) {
681 postprocessors_[i](&result);
682 }
683 return result;
684}
685
687 absl::MutexLock mutex_lock(mutex_);
688 CpSolverResponse result;
689 if (solution_pool_.BestSolutions().NumSolutions() == 0) {
690 result = GetResponseInternal({}, "");
691 } else {
692 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
693 solution = solution_pool_.BestSolutions().GetSolution(0);
694 result = GetResponseInternal(solution->variable_values, solution->info);
695 }
696 // If this is true, we postsolve and copy all of our solutions.
697 if (parameters_.fill_additional_solutions_in_response()) {
698 std::vector<int64_t> temp;
699 const int size = solution_pool_.BestSolutions().NumSolutions();
700 for (int i = 0; i < size; ++i) {
701 const auto solution = solution_pool_.BestSolutions().GetSolution(i);
702 temp = solution->variable_values;
703 for (int i = solution_postprocessors_.size(); --i >= 0;) {
704 solution_postprocessors_[i](&temp);
705 }
706 result.add_additional_solutions()->mutable_values()->Assign(temp.begin(),
707 temp.end());
708 }
709 }
710
711 // final postprocessors will print out the final log. They must be called
712 // last.
713 for (int i = final_postprocessors_.size(); --i >= 0;) {
714 final_postprocessors_[i](&result);
715 }
716
717 return result;
718}
719
721 const CpSolverResponse& response) {
722 absl::MutexLock mutex_lock(mutex_);
723 return subsolver_responses_.push_back(response);
724}
725
726void SharedResponseManager::FillObjectiveValuesInResponse(
727 CpSolverResponse* response) const {
728 if (objective_or_null_ == nullptr) return;
729 const CpObjectiveProto& obj = *objective_or_null_;
730
731 if (best_status_ == CpSolverStatus::INFEASIBLE) {
732 response->clear_objective_value();
733 response->clear_best_objective_bound();
735 return;
736 }
737
738 // Set the objective value.
739 // If we don't have any solution, we use our inner bound.
740 if (best_status_ == CpSolverStatus::UNKNOWN) {
741 response->set_objective_value(
742 ScaleObjectiveValue(obj, inner_objective_upper_bound_));
743 } else {
744 response->set_objective_value(
745 ScaleObjectiveValue(obj, best_solution_objective_value_));
746 }
747
748 // Update the best bound in the response.
750 ScaleInnerObjectiveValue(obj, inner_objective_lower_bound_));
751 response->set_best_objective_bound(
752 ScaleObjectiveValue(obj, inner_objective_lower_bound_));
753
754 // Update the primal integral.
755 response->set_gap_integral(gap_integral_);
756}
757
758std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
759SharedResponseManager::NewSolution(absl::Span<const int64_t> solution_values,
760 absl::string_view solution_info,
761 Model* model, int source_id) {
762 absl::MutexLock mutex_lock(mutex_);
763 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution> ret;
764
765 // For SAT problems, we add the solution to the solution pool for retrieval
766 // later.
767 if (objective_or_null_ == nullptr) {
769 solution.variable_values.assign(solution_values.begin(),
770 solution_values.end());
771 solution.info = solution_info;
772 solution.source_id = source_id;
773 ret = solution_pool_.Add(solution);
774 } else {
775 const int64_t objective_value =
776 ComputeInnerObjective(*objective_or_null_, solution_values);
777
778 // Add this solution to the pool, even if it is not improving.
780 solution.variable_values.assign(solution_values.begin(),
781 solution_values.end());
782 solution.rank = objective_value;
783 solution.info = solution_info;
784 solution.source_id = source_id;
785 ret = solution_pool_.Add(solution);
786
787 // Ignore any non-strictly improving solution.
788 if (objective_value > inner_objective_upper_bound_) return ret;
789
790 // Our inner_objective_lower_bound_ should be a globally valid bound, until
791 // the problem become infeasible (i.e the lb > ub) in which case the bound
792 // is no longer globally valid. Here, because we have a strictly improving
793 // solution, we shouldn't be in the infeasible setting yet.
794 DCHECK_GE(objective_value, inner_objective_lower_bound_);
795
796 DCHECK_LT(objective_value, best_solution_objective_value_);
797 best_solution_objective_value_ = objective_value;
798
799 // Update the new bound.
800 inner_objective_upper_bound_ = objective_value - 1;
801 }
802
803 // In single thread, no one is synchronizing the solution manager, so we
804 // should do it from here.
805 if (always_synchronize_) {
806 solution_pool_.Synchronize(*random_);
807 first_solution_solvers_should_stop_ = true;
808 }
809
810 // Note that the objective will be filled by
811 // FillObjectiveValuesInResponse().
812 if (objective_or_null_ == nullptr && !parameters_.enumerate_all_solutions()) {
813 UpdateBestStatus(CpSolverStatus::OPTIMAL);
814 } else {
815 UpdateBestStatus(CpSolverStatus::FEASIBLE);
816 }
817
818 // Mark model as OPTIMAL if the inner bound crossed.
819 if (objective_or_null_ != nullptr &&
820 inner_objective_lower_bound_ > inner_objective_upper_bound_) {
821 UpdateBestStatus(CpSolverStatus::OPTIMAL);
822 }
823
824 // Logging.
825 ++num_solutions_;
826
827 // Compute the post-solved response once.
828 CpSolverResponse tmp_postsolved_response;
829 if ((!search_log_callbacks_.empty() && logger_->LoggingIsEnabled()) ||
830 !callbacks_.empty()) {
831 tmp_postsolved_response =
832 GetResponseInternal(solution_values, solution_info);
833
834 // Same as FillSolveStatsInResponse() but since we already hold the mutex...
835 if (model != nullptr && !statistics_postprocessors_.empty()) {
836 for (const auto& set_stats : statistics_postprocessors_) {
837 set_stats(model, &tmp_postsolved_response);
838 }
839 }
840 }
841
842 std::string solution_message(solution_info);
843 if (tmp_postsolved_response.num_booleans() > 0) {
844 absl::StrAppend(&solution_message, " (fixed_bools=",
845 tmp_postsolved_response.num_fixed_booleans(), "/",
846 tmp_postsolved_response.num_booleans(), ")");
847 }
848
849 if (logger_->LoggingIsEnabled() && !search_log_callbacks_.empty()) {
850 for (const auto& pair : search_log_callbacks_) {
851 absl::StrAppend(&solution_message, " ",
852 pair.second(tmp_postsolved_response));
853 }
854 }
855
856 if (!status_change_callbacks_.empty()) {
857 SolverStatusChangeInfo info = GetSolverStatusChangeInfo();
858 info.change_info = solution_message;
859 info.new_best_solution = true;
860 for (const auto& callback : status_change_callbacks_) {
861 callback(info);
862 }
863 }
864
865 // Call callbacks.
866 // Note that we cannot call function that try to get the mutex_ here.
867 TestGapLimitsIfNeeded();
868 for (const auto& pair : callbacks_) {
869 pair.second(tmp_postsolved_response);
870 }
871
872#if !defined(__PORTABLE_PLATFORM__)
873 // We protect solution dumping with log_updates as LNS subsolvers share
874 // another solution manager, and we do not want to dump those.
875 if (logger_->LoggingIsEnabled() &&
876 absl::GetFlag(FLAGS_cp_model_dump_solutions)) {
877 const std::string file =
878 absl::StrCat(dump_prefix_, "solution_", num_solutions_, ".pb.txt");
879 LOG(INFO) << "Dumping solution to '" << file << "'.";
880
881 // Note that here we only want to dump the non-post-solved solution.
882 // This is only used for debugging.
883 CpSolverResponse response;
884 response.mutable_solution()->Assign(solution_values.begin(),
885 solution_values.end());
886 CHECK_OK(file::SetTextProto(file, response, file::Defaults()));
887 }
888#endif // __PORTABLE_PLATFORM__
889
890 return ret;
891}
892
894 absl::MutexLock mutex_lock(mutex_);
895 return synchronized_best_status_ == CpSolverStatus::OPTIMAL ||
896 synchronized_best_status_ == CpSolverStatus::INFEASIBLE;
897}
898
899void SharedResponseManager::UpdateBestStatus(const CpSolverStatus& status) {
900 best_status_ = status;
901 if (always_synchronize_) {
902 synchronized_best_status_ = status;
903 }
904}
905
906SolverStatusChangeInfo SharedResponseManager::GetSolverStatusChangeInfo() {
907 SolverStatusChangeInfo result;
908 if (objective_or_null_ == nullptr) return result;
909 const CpObjectiveProto& obj = *objective_or_null_;
910 const double best = ScaleObjectiveValue(obj, best_solution_objective_value_);
911 double lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
912 double ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
913 if (obj.scaling_factor() < 0) {
914 std::swap(lb, ub);
915 }
916 result.best_objective_value = best;
917 result.cur_objective_value_lb = lb;
918 result.cur_objective_value_ub = ub;
919 return result;
920}
921
923 : num_variables_(model_proto.variables_size()),
924 model_proto_(model_proto),
925 lower_bounds_(num_variables_, std::numeric_limits<int64_t>::min()),
926 upper_bounds_(num_variables_, std::numeric_limits<int64_t>::max()),
927 synchronized_lower_bounds_(num_variables_,
928 std::numeric_limits<int64_t>::min()),
929 synchronized_upper_bounds_(num_variables_,
930 std::numeric_limits<int64_t>::max()) {
931 changed_variables_since_last_synchronize_.ClearAndResize(num_variables_);
932 for (int i = 0; i < num_variables_; ++i) {
933 lower_bounds_[i] = model_proto.variables(i).domain(0);
934 const int domain_size = model_proto.variables(i).domain_size();
935 upper_bounds_[i] = model_proto.variables(i).domain(domain_size - 1);
936 synchronized_lower_bounds_[i] = lower_bounds_[i];
937 synchronized_upper_bounds_[i] = upper_bounds_[i];
938 }
939
940 // Fill symmetry data.
941 if (model_proto.has_symmetry()) {
942 const int num_vars = model_proto.variables().size();
943 std::vector<std::unique_ptr<SparsePermutation>> generators;
944 for (const SparsePermutationProto& perm :
945 model_proto.symmetry().permutations()) {
946 generators.emplace_back(CreateSparsePermutationFromProto(num_vars, perm));
947 }
948 if (generators.empty()) return;
949
950 // Get orbits in term of IntegerVariable.
951 var_to_orbit_index_ = GetOrbits(num_vars, generators);
952
953 // Fill orbits_.
954 std::vector<int> keys;
955 std::vector<int> values;
956 for (int var = 0; var < num_vars; ++var) {
957 const int orbit_index = var_to_orbit_index_[var];
958 if (orbit_index == -1) continue;
959 keys.push_back(orbit_index);
960 values.push_back(var);
961 }
962 if (keys.empty()) return;
963
964 has_symmetry_ = true;
965 orbits_.ResetFromFlatMapping(keys, values);
966
967 // Fill representative.
968 var_to_representative_.resize(num_vars);
969 for (int var = 0; var < num_vars; ++var) {
970 const int orbit_index = var_to_orbit_index_[var];
971 if (orbit_index == -1) {
972 var_to_representative_[var] = var;
973 } else {
974 var_to_representative_[var] = orbits_[orbit_index][0];
975 }
976 }
977 }
978}
979
981 const std::string& worker_name, absl::Span<const int> variables,
982 absl::Span<const int64_t> new_lower_bounds,
983 absl::Span<const int64_t> new_upper_bounds) {
984 CHECK_EQ(variables.size(), new_lower_bounds.size());
985 CHECK_EQ(variables.size(), new_upper_bounds.size());
986 int num_improvements = 0;
987 int num_symmetric_improvements = 0;
988
989 absl::MutexLock mutex_lock(mutex_);
990 for (int i = 0; i < variables.size(); ++i) {
991 int var = variables[i];
992 if (var >= num_variables_) continue;
993
994 // In the presence of symmetry we only update the representative.
995 if (has_symmetry_) {
996 var = var_to_representative_[var];
997 }
998 const int64_t old_lb = lower_bounds_[var];
999 const int64_t old_ub = upper_bounds_[var];
1000 const int64_t new_lb = new_lower_bounds[i];
1001 const int64_t new_ub = new_upper_bounds[i];
1002 const bool changed_lb = new_lb > old_lb;
1003 const bool changed_ub = new_ub < old_ub;
1004 if (!changed_lb && !changed_ub) continue;
1005
1006 VLOG(3) << worker_name << " var=" << var << " [" << old_lb << "," << old_ub
1007 << "] -> [" << new_lb << "," << new_ub << "]";
1008
1009 if (changed_lb) {
1010 if (DEBUG_MODE && !debug_solution_.empty()) {
1011 CHECK_LE(new_lb, debug_solution_[var]) << worker_name << " var=" << var;
1012 }
1013 lower_bounds_[var] = new_lb;
1014 }
1015 if (changed_ub) {
1016 if (DEBUG_MODE && !debug_solution_.empty()) {
1017 CHECK_GE(new_ub, debug_solution_[var]) << worker_name << " var=" << var;
1018 }
1019 upper_bounds_[var] = new_ub;
1020 }
1021 changed_variables_since_last_synchronize_.Set(var);
1022 num_improvements++;
1023
1024 if (has_symmetry_ && variables[i] != var) {
1025 // We count -1 so that num_improvements + num_symmetric_improvements
1026 // corresponds to the number of actual bound improvement.
1027 num_symmetric_improvements +=
1028 orbits_[var_to_orbit_index_[var]].size() - 1;
1029 }
1030 }
1031 if (num_improvements > 0) {
1032 total_num_improvements_ += num_improvements;
1033 VLOG(3) << total_num_improvements_ << "/" << num_variables_;
1034 bounds_exported_[worker_name].num_exported += num_improvements;
1035 bounds_exported_[worker_name].num_symmetric += num_symmetric_improvements;
1036 if (absl::GetFlag(FLAGS_cp_model_dump_tightened_models)) {
1037 CpModelProto tight_model = model_proto_;
1038 for (int i = 0; i < num_variables_; ++i) {
1039 IntegerVariableProto* var_proto = tight_model.mutable_variables(i);
1040
1041 int rep = i;
1042 if (has_symmetry_) rep = var_to_representative_[i];
1043 const Domain domain = ReadDomainFromProto(*var_proto)
1044 .IntersectionWith(Domain(lower_bounds_[rep],
1045 upper_bounds_[rep]));
1046 FillDomainInProto(domain, var_proto);
1047 }
1048 const std::string filename = absl::StrCat(dump_prefix_, "tighened_model_",
1049 export_counter_, ".pb.txt");
1050 LOG(INFO) << "Dumping tightened model proto to '" << filename << "'.";
1051 export_counter_++;
1052 CHECK(WriteModelProtoToFile(tight_model, filename));
1053 }
1054 }
1055}
1056
1057// TODO(user): Because we look at the non-synchronized and up to date bounds,
1058// this break determinism if two solution for the same subpart comes at the same
1059// time.
1061 absl::Span<const int64_t> solution,
1062 absl::Span<const int> variables_to_fix) {
1063 // This function shouldn't be called if we has symmetry.
1064 CHECK(!has_symmetry_);
1065 absl::MutexLock mutex_lock(mutex_);
1066
1067 // Abort if incompatible. Note that we only check the position that we are
1068 // about to fix. This should be enough. Otherwise we might never accept any
1069 // solution because the base LNS solution was not the same in some of the
1070 // variables that we fixed here.
1071 for (const int var : variables_to_fix) {
1072 const int64_t value = solution[var];
1073 if (value < lower_bounds_[var] || value > upper_bounds_[var]) {
1074 VLOG(1) << "Incompatibility in FixVariablesFromPartialSolution() "
1075 << "var: " << var << " value: " << value << " bounds: ["
1076 << lower_bounds_[var] << "," << upper_bounds_[var] << "]";
1077 return;
1078 }
1079 }
1080
1081 // Fix the variables.
1082 for (const int var : variables_to_fix) {
1083 const int64_t old_lb = lower_bounds_[var];
1084 const int64_t old_ub = upper_bounds_[var];
1085 const bool changed_lb = solution[var] > old_lb;
1086 const bool changed_ub = solution[var] < old_ub;
1087 if (!changed_lb && !changed_ub) continue;
1088
1089 lower_bounds_[var] = solution[var];
1090 upper_bounds_[var] = solution[var];
1091 changed_variables_since_last_synchronize_.Set(var);
1092
1093 // This is problematic as we might find a different partial solution.
1094 // To allow for further investigation, we currently fix it to the debug
1095 // solution instead.
1096 if (DEBUG_MODE && !debug_solution_.empty()) {
1097 if (solution[var] != debug_solution_[var]) {
1098 LOG(INFO) << "Fixing to a different solution for var=" << var
1099 << " debug=" << debug_solution_[var]
1100 << " partial=" << solution[var];
1101 lower_bounds_[var] = debug_solution_[var];
1102 upper_bounds_[var] = debug_solution_[var];
1103 }
1104 }
1105 }
1106}
1107
1109 absl::MutexLock mutex_lock(mutex_);
1110 for (const int var :
1111 changed_variables_since_last_synchronize_.PositionsSetAtLeastOnce()) {
1112 DCHECK(!has_symmetry_ || var_to_representative_[var] == var);
1113 synchronized_lower_bounds_[var] = lower_bounds_[var];
1114 synchronized_upper_bounds_[var] = upper_bounds_[var];
1115 for (int j = 0; j < id_to_changed_variables_.size(); ++j) {
1116 id_to_changed_variables_[j].Set(var);
1117 }
1118 }
1119 changed_variables_since_last_synchronize_.ResetAllToFalse();
1120}
1121
1123 absl::MutexLock mutex_lock(mutex_);
1124 const int id = id_to_changed_variables_.size();
1125 id_to_changed_variables_.resize(id + 1);
1126 id_to_changed_variables_[id].ClearAndResize(num_variables_);
1127 for (int var = 0; var < num_variables_; ++var) {
1128 const int64_t lb = model_proto_.variables(var).domain(0);
1129 const int domain_size = model_proto_.variables(var).domain_size();
1130 const int64_t ub = model_proto_.variables(var).domain(domain_size - 1);
1131 if (lb != synchronized_lower_bounds_[var] ||
1132 ub != synchronized_upper_bounds_[var]) {
1133 DCHECK(!has_symmetry_ || var_to_representative_[var] == var);
1134 id_to_changed_variables_[id].Set(var);
1135 }
1136 }
1137 return id;
1138}
1139
1141 int id, std::vector<int>* variables, std::vector<int64_t>* new_lower_bounds,
1142 std::vector<int64_t>* new_upper_bounds) {
1143 variables->clear();
1144 new_lower_bounds->clear();
1145 new_upper_bounds->clear();
1146
1147 {
1148 absl::MutexLock mutex_lock(mutex_);
1149 for (const int var :
1150 id_to_changed_variables_[id].PositionsSetAtLeastOnce()) {
1151 DCHECK(!has_symmetry_ || var_to_representative_[var] == var);
1152 variables->push_back(var);
1153 }
1154 id_to_changed_variables_[id].ResetAllToFalse();
1155
1156 // We need to report the bounds in a deterministic order as it is difficult
1157 // to guarantee that nothing depend on the order in which the new bounds are
1158 // processed.
1159 absl::c_sort(*variables);
1160 for (const int var : *variables) {
1161 new_lower_bounds->push_back(synchronized_lower_bounds_[var]);
1162 new_upper_bounds->push_back(synchronized_upper_bounds_[var]);
1163 }
1164 }
1165
1166 // Now that the mutex is released, we can add all symmetric version if any.
1167 // Note that alternatively we could do that in the client side, but the
1168 // complexity will be the same, we will just save some memory that is usually
1169 // just reused.
1170 if (has_symmetry_) {
1171 const int old_size = variables->size();
1172 for (int i = 0; i < old_size; ++i) {
1173 const int var = (*variables)[i];
1174 const int orbit_index = var_to_orbit_index_[var];
1175 if (orbit_index == -1) continue;
1176
1177 const int64_t lb = (*new_lower_bounds)[i];
1178 const int64_t ub = (*new_upper_bounds)[i];
1179 const auto orbit = orbits_[orbit_index];
1180 CHECK_EQ(var, orbit[0]);
1181 for (const int other : orbit.subspan(1)) {
1182 variables->push_back(other);
1183 new_lower_bounds->push_back(lb);
1184 new_upper_bounds->push_back(ub);
1185 }
1186 }
1187 }
1188}
1189
1190void SharedBoundsManager::UpdateDomains(std::vector<Domain>* domains) {
1191 absl::MutexLock mutex_lock(mutex_);
1192 CHECK_EQ(domains->size(), synchronized_lower_bounds_.size());
1193 for (int var = 0; var < domains->size(); ++var) {
1194 (*domains)[var] = (*domains)[var].IntersectionWith(Domain(
1195 synchronized_lower_bounds_[var], synchronized_upper_bounds_[var]));
1196 }
1197}
1198
1200 absl::MutexLock mutex_lock(mutex_);
1201 if (!bounds_exported_.empty()) {
1202 std::vector<std::vector<std::string>> table;
1203 table.push_back({"Improving bounds shared", "Num", "Sym"});
1204 for (const auto& entry : bounds_exported_) {
1205 table.push_back({FormatName(entry.first),
1206 FormatCounter(entry.second.num_exported),
1207 FormatCounter(entry.second.num_symmetric)});
1208 }
1209 SOLVER_LOG(logger, FormatTable(table));
1210 }
1211}
1212
1213int SharedBoundsManager::NumBoundsExported(absl::string_view worker_name) {
1214 absl::MutexLock mutex_lock(mutex_);
1215 const auto it = bounds_exported_.find(worker_name);
1216 if (it == bounds_exported_.end()) return 0;
1217 return it->second.num_exported;
1218}
1219
1221 for (auto& buffer : clauses_by_size_) {
1222 buffer.reserve(kMaxLiteralsPerBatch);
1223 }
1224 fingerprints_.reserve(kMaxFingerprints);
1225}
1226
1227bool UniqueClauseStream::Add(absl::Span<const int> clause, int lbd) {
1228 if (!BlockClause(clause) || lbd > lbd_threshold_) return false;
1229 std::vector<int>* buffer = MutableBufferForSize(clause.size());
1230 CHECK_NE(buffer, nullptr);
1231 if (buffer->size() + clause.size() <= kMaxLiteralsPerBatch) {
1232 buffer->insert(buffer->end(), clause.begin(), clause.end());
1233 } else {
1234 // Maybe replace an old buffered clause of the same size if it has a smaller
1235 // hash value. This means that the buffer will contain a deterministic
1236 // sample of the clauses added independent of insertion order.
1237 const int64_t replaced_clause_id =
1238 HashClause(clause, 1) % NumClausesOfSize(clause.size());
1239 absl::Span<int> replaced_clause = absl::MakeSpan(*buffer).subspan(
1240 replaced_clause_id * clause.size(), clause.size());
1241 dropped_literals_since_last_batch_ += clause.size();
1242 if (HashClause(clause, 2) >= HashClause(replaced_clause, 2)) return false;
1243 absl::c_copy(clause, replaced_clause.begin());
1244 }
1245 return true;
1246}
1247
1248bool UniqueClauseStream::BlockClause(absl::Span<const int> clause) {
1249 if (clause.size() > kMaxClauseSize) return false;
1250 if (clause.size() <= 2) return false;
1251 const auto hash = HashClause(clause);
1252 return fingerprints_.emplace(hash).second &&
1253 !old_fingerprints_.contains(hash);
1254}
1255
1259 int to_fill = kMaxLiteralsPerBatch;
1260 for (int size = kMinClauseSize; size <= kMaxClauseSize; ++size) {
1261 CHECK_EQ(NumLiteralsOfSize(size) % size, 0);
1262 std::vector<int>* buffer = MutableBufferForSize(size);
1263 while (to_fill >= size && !buffer->empty()) {
1264 batch.Add(NextClause(size));
1265 to_fill -= size;
1266 PopClause(size);
1267 }
1268 if (to_fill < size) {
1269 dropped_literals_since_last_batch_ += buffer->size();
1270 buffer->clear();
1271 }
1272 }
1273
1274 if (fingerprints_.size() >= kMaxFingerprints / 2) {
1275 VLOG(2) << "Clearing fingerprints: " << fingerprints_.size() / 1024 << "Ki";
1276 std::swap(fingerprints_, old_fingerprints_);
1277 fingerprints_.clear();
1278 fingerprints_.reserve(kMaxFingerprints);
1279 }
1280
1281 if (to_fill > kMaxLiteralsPerBatch / 2 && lbd_threshold_ < kMaxLbd) {
1282 lbd_threshold_ += 1;
1283 VLOG(2) << "Inc lbd: " << lbd_threshold_;
1284 } else if (dropped_literals_since_last_batch_ > 0 &&
1285 lbd_threshold_ > kMinLbd) {
1286 lbd_threshold_ -= 1;
1287 VLOG(2) << "Dec lbd: " << lbd_threshold_;
1288 }
1289 dropped_literals_since_last_batch_ = 0;
1290 return batch;
1291}
1292
1294 int result = 0;
1295 for (const auto& buffer : clauses_by_size_) {
1296 result += buffer.size();
1297 }
1298 return result;
1299}
1300
1301size_t UniqueClauseStream::HashClause(absl::Span<const int> clause,
1302 size_t hash_seed) {
1303 size_t hash = absl::HashOf(hash_seed, clause.size());
1304 for (int i = 0; i < clause.size(); ++i) {
1305 hash ^= absl::HashOf(clause[i], hash_seed);
1306 }
1307 return hash;
1308}
1309
1310absl::Span<const int> UniqueClauseStream::NextClause(int size) const {
1311 absl::Span<const int> buffer = BufferForSize(size);
1312 return buffer.subspan(buffer.size() - size, size);
1313}
1314
1315void UniqueClauseStream::PopClause(int size) {
1316 std::vector<int>* buffer = MutableBufferForSize(size);
1317 buffer->erase(buffer->end() - size, buffer->end());
1318}
1319
1320int UniqueClauseStream::NumClausesOfSize(int size) const {
1321 return NumLiteralsOfSize(size) / size;
1322}
1323
1325 return BufferForSize(size).size();
1326}
1327
1329 : always_synchronize_(always_synchronize) {}
1330
1331int SharedClausesManager::RegisterNewId(absl::string_view worker_name,
1332 bool may_terminate_early) {
1333 absl::MutexLock mutex_lock(mutex_);
1334 num_full_workers_ += may_terminate_early ? 0 : 1;
1335 const int id = id_to_last_processed_binary_clause_.size();
1336 id_to_last_processed_binary_clause_.resize(id + 1, 0);
1337 id_to_last_returned_batch_.resize(id + 1, -1);
1338 id_to_last_finished_batch_.resize(id + 1, -1);
1339 id_to_num_imported_.resize(id + 1, 0);
1340 id_to_num_exported_.resize(id + 1, 0);
1341 id_to_worker_name_.resize(id + 1);
1342 id_to_worker_name_[id] = worker_name;
1343 return id;
1344}
1345
1346int SharedLinear2Bounds::RegisterNewId(std::string worker_name) {
1347 absl::MutexLock mutex_lock(mutex_);
1348 const int id = id_to_worker_name_.size();
1349
1350 id_to_stats_.resize(id + 1);
1351 id_to_worker_name_.resize(id + 1);
1352 id_to_worker_name_[id] = worker_name;
1353 return id;
1354}
1355
1356bool SharedClausesManager::ShouldReadBatch(int reader_id, int writer_id) {
1357 return reader_id != writer_id;
1358}
1359
1360void SharedClausesManager::AddBinaryClause(int id, int lit1, int lit2) {
1361 if (lit2 < lit1) std::swap(lit1, lit2);
1362 const auto p = std::make_pair(lit1, lit2);
1363
1364 absl::MutexLock mutex_lock(mutex_);
1365 const auto [unused_it, inserted] = added_binary_clauses_set_.insert(p);
1366 if (inserted) {
1367 added_binary_clauses_.push_back(p);
1368 if (always_synchronize_) ++last_visible_binary_clause_;
1369 id_to_num_exported_[id]++;
1370
1371 // Small optim. If the worker is already up to date with clauses to import,
1372 // we can mark this new clause as already seen.
1373 if (id_to_last_processed_binary_clause_[id] ==
1374 added_binary_clauses_.size() - 1) {
1375 id_to_last_processed_binary_clause_[id]++;
1376 }
1377 }
1378}
1379
1381 absl::MutexLock mutex_lock(mutex_);
1382 id_to_num_exported_[id] += batch.size();
1383 pending_batches_.push_back(std::move(batch));
1384}
1385
1387 std::vector<absl::Span<const int>> result;
1388 {
1389 absl::MutexLock mutex_lock(mutex_);
1390 id_to_last_finished_batch_[id] = id_to_last_returned_batch_[id];
1391 if (id_to_last_returned_batch_[id] + 1 < batches_.size()) {
1392 id_to_last_returned_batch_[id] += 1;
1393 return batches_[id_to_last_returned_batch_[id]];
1394 }
1395 }
1396 static CompactVectorVector<int>* const empty_batch =
1398 return *empty_batch;
1399}
1400
1402 int id, std::vector<std::pair<int, int>>* new_clauses) {
1403 new_clauses->clear();
1404 absl::MutexLock mutex_lock(mutex_);
1405 const int last_binary_clause_seen = id_to_last_processed_binary_clause_[id];
1406 if (last_binary_clause_seen >= last_visible_binary_clause_) return;
1407
1408 new_clauses->assign(
1409 added_binary_clauses_.begin() + last_binary_clause_seen,
1410 added_binary_clauses_.begin() + last_visible_binary_clause_);
1411 id_to_last_processed_binary_clause_[id] = last_visible_binary_clause_;
1412}
1413
1414void SharedClausesManager::NotifyNumImported(int id, int64_t num_imported) {
1415 absl::MutexLock mutex_lock(mutex_);
1416 id_to_num_imported_[id] += num_imported;
1417}
1418
1420 absl::MutexLock mutex_lock(mutex_);
1421 std::vector<std::tuple<std::string, int64_t, int64_t, int64_t, int64_t>>
1422 name_to_table_line;
1423 for (int id = 0; id < id_to_num_exported_.size(); ++id) {
1424 name_to_table_line.push_back(
1425 {id_to_worker_name_[id], id_to_num_exported_[id],
1426 id_to_num_imported_[id], id_to_last_processed_binary_clause_[id],
1427 last_visible_binary_clause_});
1428 }
1429 if (!name_to_table_line.empty()) {
1430 absl::c_sort(name_to_table_line);
1431 std::vector<std::vector<std::string>> table;
1432 table.push_back({"Clauses shared", "#Exported", "#Imported", "#BinaryRead",
1433 "#BinaryTotal"});
1434 for (const auto& [name, exported, imported, binary_read, binary_total] :
1435 name_to_table_line) {
1436 table.push_back({FormatName(name), FormatCounter(exported),
1437 FormatCounter(imported), FormatCounter(binary_read),
1438 FormatCounter(binary_total)});
1439 }
1440 SOLVER_LOG(logger, FormatTable(table));
1441 }
1442}
1443
1444// TODO(user): Add some library to simplify this "transposition". Ideally we
1445// could merge small table with few columns. I am thinking list (row_name,
1446// col_name, count) + function that create table?
1448 absl::MutexLock mutex_lock(mutex_);
1449 absl::btree_map<std::string, Stats> name_to_table_line;
1450 for (int id = 0; id < id_to_stats_.size(); ++id) {
1451 const Stats stats = id_to_stats_[id];
1452 if (!stats.empty()) {
1453 name_to_table_line[id_to_worker_name_[id]] = stats;
1454 }
1455 }
1456 for (int import_id = 0; import_id < import_id_to_index_.size(); ++import_id) {
1457 name_to_table_line[import_id_to_name_[import_id]].num_imported =
1458 import_id_to_num_imported_[import_id];
1459 }
1460 if (!name_to_table_line.empty()) {
1461 std::vector<std::vector<std::string>> table;
1462 table.push_back({"Linear2 shared", "New", "Updated", "Imported"});
1463 for (const auto& [name, stats] : name_to_table_line) {
1464 table.push_back({FormatName(name), FormatCounter(stats.num_new),
1465 FormatCounter(stats.num_update),
1466 FormatCounter(stats.num_imported)});
1467 }
1468 SOLVER_LOG(logger, FormatTable(table));
1469 }
1470}
1471
1473 std::vector<CompactVectorVector<int>> batches_to_merge;
1474 {
1475 absl::MutexLock mutex_lock(mutex_);
1476 last_visible_binary_clause_ = added_binary_clauses_.size();
1477 const int num_workers = id_to_last_processed_binary_clause_.size();
1478 if (num_workers <= 1) return;
1479
1480 if (pending_batches_.size() >= num_full_workers_) {
1481 batches_to_merge = std::move(pending_batches_);
1482 }
1483
1484 // Delete batches that have been consumed by all workers.
1485 // Keep a few batches around for startup (min finished batch doesn't count
1486 // workers that haven't registered yet).
1487 if (batches_.size() > kMinBatches) {
1488 const int min_finished_batch =
1489 std::min<int>(batches_.size() - kMinBatches,
1490 *absl::c_min_element(id_to_last_finished_batch_) + 1);
1491 for (int i = 0; i < min_finished_batch; ++i) {
1492 VLOG(2) << "Erasing batch";
1493 batches_.pop_front();
1494 }
1495 for (int id = 0; id < id_to_last_finished_batch_.size(); ++id) {
1496 id_to_last_returned_batch_[id] -= min_finished_batch;
1497 id_to_last_finished_batch_[id] -= min_finished_batch;
1498 }
1499 }
1500 // TODO(user): We could cleanup binary clauses that have been consumed.
1501 }
1502 if (batches_to_merge.empty()) return;
1503 UniqueClauseStream next_batch;
1504 for (const auto& batch : batches_to_merge) {
1505 for (int i = 0; i < batch.size(); ++i) {
1506 next_batch.Add(batch[i]);
1507 }
1508 }
1509 if (next_batch.NumBufferedLiterals() > 0) {
1510 absl::MutexLock mutex_lock(mutex_);
1511 VLOG(2) << "Merging batch";
1512 batches_.push_back(next_batch.NextBatch());
1513 }
1514}
1515
1516void SharedLinear2Bounds::Add(int id, Key expr, IntegerValue lb,
1517 IntegerValue ub) {
1518 DCHECK(expr.IsCanonicalized()) << expr;
1519
1520 absl::MutexLock mutex_lock(mutex_);
1521 auto [it, inserted] = shared_bounds_.insert({expr, {lb, ub}});
1522 if (inserted) {
1523 // It is new.
1524 id_to_stats_[id].num_new++;
1525 newly_updated_keys_.push_back(expr);
1526 } else {
1527 // Update the individual bounds if the new ones are better.
1528 auto& bounds = it->second;
1529 const bool update_lb = lb > bounds.first;
1530 if (update_lb) bounds.first = lb;
1531 const bool update_ub = ub < bounds.second;
1532 if (update_ub) bounds.second = ub;
1533 if (update_lb || update_ub) {
1534 id_to_stats_[id].num_update++;
1535 newly_updated_keys_.push_back(expr);
1536 }
1537 }
1538}
1539
1541 absl::MutexLock mutex_lock(mutex_);
1542 const int import_id = import_id_to_index_.size();
1543 import_id_to_name_.push_back(name);
1544 import_id_to_index_.push_back(0);
1545 import_id_to_num_imported_.push_back(0);
1546 return import_id;
1547}
1548
1549std::vector<
1550 std::pair<SharedLinear2Bounds::Key, std::pair<IntegerValue, IntegerValue>>>
1552 std::vector<std::pair<Key, std::pair<IntegerValue, IntegerValue>>> result;
1553
1554 absl::MutexLock mutex_lock(mutex_);
1555 MaybeCompressNewlyUpdateKeys();
1556 const int size = newly_updated_keys_.size();
1557 for (int i = import_id_to_index_[import_id]; i < size; ++i) {
1558 const auto& key = newly_updated_keys_[i];
1559 result.push_back({key, shared_bounds_[key]});
1560 }
1561 import_id_to_index_[import_id] = size;
1562 return result;
1563}
1564
1565void SharedLinear2Bounds::MaybeCompressNewlyUpdateKeys() {
1566 int min_index = 0;
1567 for (const int index : import_id_to_index_) {
1568 min_index = std::min(index, min_index);
1569 }
1570 if (min_index == 0) return;
1571
1572 newly_updated_keys_.erase(newly_updated_keys_.begin(),
1573 newly_updated_keys_.begin() + min_index);
1574 for (int& index_ref : import_id_to_index_) {
1575 index_ref -= min_index;
1576 }
1577}
1578
1580 absl::Span<const std::pair<std::string, int64_t>> stats) {
1581 absl::MutexLock mutex_lock(mutex_);
1582 for (const auto& [key, count] : stats) {
1583 stats_[key] += count;
1584 }
1585}
1586
1588 absl::MutexLock mutex_lock(mutex_);
1589 if (stats_.empty()) return;
1590
1591 SOLVER_LOG(logger, "Stats across workers (summed):");
1592 std::vector<std::pair<std::string, int64_t>> to_sort_;
1593 for (const auto& [key, count] : stats_) {
1594 to_sort_.push_back({key, count});
1595 }
1596 std::sort(to_sort_.begin(), to_sort_.end());
1597 for (const auto& [key, count] : to_sort_) {
1598 SOLVER_LOG(logger, " ", key, ": ", FormatCounter(count));
1599 }
1600 SOLVER_LOG(logger, "");
1601}
1602
1604 : num_subsolvers_(0),
1605 num_valid_proofs_(0),
1606 num_invalid_proofs_(0),
1607 num_unknown_proofs_(0),
1608 lrat_check_enabled_(false),
1609 drat_check_enabled_(false),
1610 num_assumed_clauses_(0),
1611 walltime_in_seconds_(0.0) {}
1612
1614 absl::MutexLock mutex_lock(mutex_);
1615 return num_subsolvers_++;
1616}
1617
1619 DratChecker::Status status, bool lrat_check_enabled,
1620 bool drat_check_enabled, int num_assumed_clauses,
1621 double walltime_in_seconds) {
1622 absl::MutexLock mutex_lock(mutex_);
1623 if (status == DratChecker::Status::VALID) {
1624 num_valid_proofs_++;
1625 } else if (status == DratChecker::Status::INVALID) {
1626 num_invalid_proofs_++;
1627 } else if (status == DratChecker::Status::UNKNOWN) {
1628 num_unknown_proofs_++;
1629 }
1630 lrat_check_enabled_ |= lrat_check_enabled;
1631 drat_check_enabled_ |= drat_check_enabled;
1632 num_assumed_clauses_ += num_assumed_clauses;
1633 if (drat_check_enabled) {
1634 walltime_in_seconds_ += walltime_in_seconds;
1635 }
1636}
1637
1638void SharedLratProofStatus::NewProofFile(absl::string_view filename) {
1639 absl::MutexLock mutex_lock(mutex_);
1640 proof_filenames_.push_back(std::string(filename));
1641}
1642
1644 absl::MutexLock mutex_lock(mutex_);
1645 return proof_filenames_;
1646}
1647
1649 absl::MutexLock mutex_lock(mutex_);
1650 if (lrat_check_enabled_ || drat_check_enabled_) {
1651 if (num_valid_proofs_ == num_subsolvers_) {
1652 if (num_assumed_clauses_ > 0) {
1653 SOLVER_LOG(logger, "LRAT_status: VALID_WITH_ASSUMED_CLAUSES");
1654 } else {
1655 SOLVER_LOG(logger, "LRAT_status: VALID");
1656 }
1657 } else if (num_invalid_proofs_ > 0) {
1658 SOLVER_LOG(logger, "LRAT_status: INVALID");
1659 } else {
1660 SOLVER_LOG(logger, "LRAT_status: UNKNOWN");
1661 }
1662 if (drat_check_enabled_) {
1663 SOLVER_LOG(logger, "DRAT_walltime: ", walltime_in_seconds_);
1664 }
1665 } else {
1666 // Always log an LRAT status to make it easier to extract it from a
1667 // multirun result with awk.
1668 SOLVER_LOG(logger, "LRAT_status: NA");
1669 if (drat_check_enabled_) {
1670 SOLVER_LOG(logger, "DRAT_walltime: NA");
1671 }
1672 }
1673}
1674
1675} // namespace sat
1676} // namespace operations_research
int Add(absl::Span< const V > values)
Definition util.h:935
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
::operations_research::sat::IntegerVariableProto *PROTOBUF_NONNULL mutable_variables(int index)
const ::operations_research::sat::CpObjectiveProto & objective() const
::operations_research::sat::CpSolverSolution *PROTOBUF_NONNULL add_additional_solutions()
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_solution()
void set_solution_info(Arg_ &&arg, Args_... args)
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_sufficient_assumptions_for_infeasibility()
void set_status(::operations_research::sat::CpSolverStatus value)
::operations_research::sat::CpSolverStatus status() const
void set_inner_objective_lower_bound(::int64_t value)
void MergeFrom(const CpSolverResponse &from)
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_values()
int NumBoundsExported(absl::string_view worker_name)
void UpdateDomains(std::vector< Domain > *domains)
void ReportPotentialNewBounds(const std::string &worker_name, absl::Span< const int > variables, absl::Span< const int64_t > new_lower_bounds, absl::Span< const int64_t > new_upper_bounds)
void FixVariablesFromPartialSolution(absl::Span< const int64_t > solution, absl::Span< const int > variables_to_fix)
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
SharedBoundsManager(const CpModelProto &model_proto)
int RegisterNewId(absl::string_view worker_name, bool may_terminate_early)
void AddBinaryClause(int id, int lit1, int lit2)
void AddBatch(int id, CompactVectorVector< int > batch)
void GetUnseenBinaryClauses(int id, std::vector< std::pair< int, int > > *new_clauses)
const CompactVectorVector< int > & GetUnseenClauses(int id)
void NotifyNumImported(int id, int64_t num_imported)
void AddSolution(const std::vector< double > &lp_solution)
void NewLPSolution(std::vector< double > lp_solution)
std::vector< std::pair< Key, std::pair< IntegerValue, IntegerValue > > > NewlyUpdatedBounds(int import_id)
void Add(int id, Key expr, IntegerValue lb, IntegerValue ub)
void NewProofFile(absl::string_view filename)
void NewSubsolverProofStatus(DratChecker::Status status, bool lrat_check_enabled, bool drat_check_enabled, int num_assumed_clauses, double walltime_in_seconds)
void AppendResponseToBeMerged(const CpSolverResponse &response)
void AddUnsatCore(const std::vector< int > &core)
void AddStatisticsPostprocessor(std::function< void(Model *, CpSolverResponse *)> postprocessor)
void LogMessage(absl::string_view prefix, absl::string_view message)
void SetSynchronizationMode(bool always_synchronize)
int AddLogCallback(std::function< std::string(const CpSolverResponse &)> callback)
int AddBestBoundCallback(std::function< void(double)> callback)
void AddStatusChangeCallback(std::function< void(const SolverStatusChangeInfo &)> callback)
void InitializeObjective(const CpModelProto &cp_model)
void SetGapLimitsFromParameters(const SatParameters &parameters)
void AddFinalResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
void AddResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
int AddSolutionCallback(std::function< void(const CpSolverResponse &)> callback)
void AddSolutionPostprocessor(std::function< void(std::vector< int64_t > *)> postprocessor)
void FillSolveStatsInResponse(Model *model, CpSolverResponse *response)
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > NewSolution(absl::Span< const int64_t > solution_values, absl::string_view solution_info, Model *model=nullptr, int source_id=-1)
void LogMessageWithThrottling(absl::string_view prefix, absl::string_view message)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
void NotifyThatImprovingProblemIsInfeasible(absl::string_view worker_info)
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > Add(SharedSolutionRepository< int64_t >::Solution solution)
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
bool BlockClause(absl::Span< const int > clause)
static size_t HashClause(absl::Span< const int > clause, size_t hash_seed=0)
bool Add(absl::Span< const int > clause, int lbd=2)
Definition file.cc:327
absl::Status SetTextProto(absl::string_view file_name, const google::protobuf::Message &proto, Options options)
Definition file.cc:448
Options Defaults()
Definition file.h:86
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, absl::Span< const int64_t > solution)
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
std::vector< int > GetOrbits(int n, absl::Span< const std::unique_ptr< SparsePermutation > > generators)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
int64_t ScaleInnerObjectiveValue(const CpObjectiveProto &proto, int64_t value)
std::string FormatName(absl::string_view name)
Definition util.h:342
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
Definition util.cc:61
OR-Tools root namespace.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
const bool DEBUG_MODE
Definition radix_sort.h:266
std::string FormatCounter(int64_t num)
Definition logging.cc:30
STL namespace.
if(!yyg->yy_init)
Definition parser.yy.cc:966
ABSL_FLAG(bool, cp_model_dump_solutions, false, "DEBUG ONLY. If true, all the intermediate solution will be dumped " "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.")
#define SOLVER_LOG(logger,...)
Definition logging.h:114