Google OR-Tools v9.11
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-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 <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 <string>
29#include <utility>
30#include <vector>
31
32#include "absl/hash/hash.h"
33#include "absl/time/time.h"
35#include "ortools/base/timer.h"
36#if !defined(__PORTABLE_PLATFORM__)
39#endif // __PORTABLE_PLATFORM__
40#include "absl/algorithm/container.h"
41#include "absl/container/btree_map.h"
42#include "absl/container/flat_hash_map.h"
43#include "absl/container/flat_hash_set.h"
44#include "absl/flags/flag.h"
45#include "absl/log/check.h"
46#include "absl/strings/str_cat.h"
47#include "absl/strings/str_format.h"
48#include "absl/strings/string_view.h"
49#include "absl/synchronization/mutex.h"
50#include "absl/types/span.h"
51#include "ortools/sat/cp_model.pb.h"
53#include "ortools/sat/integer.h"
54#include "ortools/sat/model.h"
56#include "ortools/sat/sat_parameters.pb.h"
58#include "ortools/sat/util.h"
59#include "ortools/util/bitset.h"
63
64ABSL_FLAG(bool, cp_model_dump_solutions, false,
65 "DEBUG ONLY. If true, all the intermediate solution will be dumped "
66 "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.");
67ABSL_FLAG(bool, cp_model_dump_tightened_models, false,
68 "DEBUG ONLY. If true, dump tightened models incoporating all bounds "
69 "changes under '\"FLAGS_cp_model_dump_prefix\" + "
70 "\"tight_model_xxx.pb.txt\"'.");
71
72namespace operations_research {
73namespace sat {
74
76 std::vector<double> lp_solution) {
77 if (lp_solution.empty()) return;
78
79 // Add this solution to the pool.
81 solution.variable_values = std::move(lp_solution);
82
83 // We always prefer to keep the solution from the last synchronize batch.
84 absl::MutexLock mutex_lock(&mutex_);
85 solution.rank = -num_synchronization_;
86 ++num_added_;
87 new_solutions_.push_back(solution);
88}
89
91 const std::vector<double>& lp_solution) {
92 absl::MutexLock mutex_lock(&mutex_);
93 ++num_added_;
94 solutions_.push_back(lp_solution);
95 if (solutions_.size() > 100) solutions_.pop_front();
96}
97
99 absl::MutexLock mutex_lock(&mutex_);
100 return !solutions_.empty();
101}
102
104 absl::MutexLock mutex_lock(&mutex_);
105 if (solutions_.empty()) return {};
106
107 ++num_queried_;
108 std::vector<double> solution = std::move(solutions_.back());
109 solutions_.pop_back();
110 return solution;
111}
112
114 : parameters_(*model->GetOrCreate<SatParameters>()),
115 wall_timer_(*model->GetOrCreate<WallTimer>()),
116 shared_time_limit_(model->GetOrCreate<ModelSharedTimeLimit>()),
117 solutions_(parameters_.solution_pool_size(), "feasible solutions"),
118 logger_(model->GetOrCreate<SolverLogger>()) {
119 bounds_logging_id_ = logger_->GetNewThrottledId();
120}
121
122namespace {
123
124std::string ProgressMessage(const std::string& event_or_solution_count,
125 double time_in_seconds, double obj_best,
126 double obj_lb, double obj_ub,
127 const std::string& solution_info) {
128 const std::string obj_next =
129 obj_lb <= obj_ub ? absl::StrFormat("next:[%.9g,%.9g]", obj_lb, obj_ub)
130 : "next:[]";
131 return absl::StrFormat("#%-5s %6.2fs best:%-5.9g %-15s %s",
132 event_or_solution_count, time_in_seconds, obj_best,
133 obj_next, solution_info);
134}
135
136std::string SatProgressMessage(const std::string& event_or_solution_count,
137 double time_in_seconds,
138 const std::string& solution_info) {
139 return absl::StrFormat("#%-5s %6.2fs %s", event_or_solution_count,
140 time_in_seconds, solution_info);
141}
142
143} // namespace
144
145void FillSolveStatsInResponse(Model* model, CpSolverResponse* response) {
146 if (model == nullptr) return;
147 auto* sat_solver = model->GetOrCreate<SatSolver>();
148 auto* integer_trail = model->Get<IntegerTrail>();
149 response->set_num_booleans(sat_solver->NumVariables());
150 response->set_num_branches(sat_solver->num_branches());
151 response->set_num_conflicts(sat_solver->num_failures());
152 response->set_num_binary_propagations(sat_solver->num_propagations());
153 response->set_num_restarts(sat_solver->num_restarts());
154
155 response->set_num_integers(
156 integer_trail == nullptr
157 ? 0
158 : integer_trail->NumIntegerVariables().value() / 2);
159 response->set_num_integer_propagations(
160 integer_trail == nullptr ? 0 : integer_trail->num_enqueues());
161
162 // TODO(user): find a way to clear all stats fields that might be set by
163 // one of the callback.
164 response->set_num_lp_iterations(0);
165 for (const auto& set_stats :
167 set_stats(response);
168 }
169}
170
171void SharedResponseManager::LogMessage(absl::string_view prefix,
172 absl::string_view message) {
173 absl::MutexLock mutex_lock(&mutex_);
174 SOLVER_LOG(logger_, absl::StrFormat("#%-5s %6.2fs %s", prefix,
175 wall_timer_.Get(), message));
176}
177
179 const std::string& prefix, const std::string& message) {
180 absl::MutexLock mutex_lock(&mutex_);
181
182 int id;
183 auto it = throttling_ids_.find(prefix);
184 if (it == throttling_ids_.end()) {
185 id = throttling_ids_[prefix] = logger_->GetNewThrottledId();
186 } else {
187 id = it->second;
188 }
189 logger_->ThrottledLog(id, absl::StrFormat("#%-5s %6.2fs %s", prefix,
190 wall_timer_.Get(), message));
191}
192
194 absl::MutexLock mutex_lock(&mutex_);
195
196 return logger_->LoggingIsEnabled();
197}
198
199void SharedResponseManager::InitializeObjective(const CpModelProto& cp_model) {
200 if (cp_model.has_objective()) {
201 objective_or_null_ = &cp_model.objective();
202 const Domain domain = ReadDomainFromProto(cp_model.objective());
203 if (!domain.IsEmpty()) {
204 UpdateInnerObjectiveBounds("initial_domain", IntegerValue(domain.Min()),
205 IntegerValue(domain.Max()));
206 }
207 } else {
208 objective_or_null_ = nullptr;
209 }
210}
211
213 absl::MutexLock mutex_lock(&mutex_);
214 always_synchronize_ = always_synchronize;
215}
216
218 absl::MutexLock mutex_lock(&mutex_);
219 update_integral_on_each_change_ = set;
220}
221
223 absl::MutexLock mutex_lock(&mutex_);
224 UpdateGapIntegralInternal();
225}
226
227void SharedResponseManager::UpdateGapIntegralInternal() {
228 if (objective_or_null_ == nullptr) return;
229
230 const double current_time = shared_time_limit_->GetElapsedDeterministicTime();
231 const double time_delta = current_time - last_gap_integral_time_stamp_;
232
233 // We use the log of the absolute objective gap.
234 //
235 // Using the log should count no solution as just log(2*64) = 18, and
236 // otherwise just compare order of magnitude which seems nice. Also, It is
237 // more easy to compare the primal integral with the total time.
238 const CpObjectiveProto& obj = *objective_or_null_;
239 const double factor =
240 obj.scaling_factor() != 0.0 ? std::abs(obj.scaling_factor()) : 1.0;
241 const double bounds_delta = std::log(1 + factor * last_absolute_gap_);
242 gap_integral_ += time_delta * bounds_delta;
243
244 // Update with new value.
245 last_gap_integral_time_stamp_ = current_time;
246 last_absolute_gap_ =
247 std::max(0.0, static_cast<double>(inner_objective_upper_bound_) -
248 static_cast<double>(inner_objective_lower_bound_));
249}
250
252 const SatParameters& parameters) {
253 absl::MutexLock mutex_lock(&mutex_);
254 if (objective_or_null_ == nullptr) return;
255 absolute_gap_limit_ = parameters.absolute_gap_limit();
256 relative_gap_limit_ = parameters.relative_gap_limit();
257}
258
259void SharedResponseManager::TestGapLimitsIfNeeded() {
260 // This is called on each internal limit change, so it is a good place to
261 // update the integral. Note that this is not called at the end of the search
262 // though.
263 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
264
265 // Abort if there is not limit set, if the gap is not defined or if we already
266 // proved optimality or infeasibility.
267 if (absolute_gap_limit_ == 0 && relative_gap_limit_ == 0) return;
268 if (best_solution_objective_value_ >= kMaxIntegerValue) return;
269 if (inner_objective_lower_bound_ <= kMinIntegerValue) return;
270 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) return;
271
272 const CpObjectiveProto& obj = *objective_or_null_;
273 const double user_best =
274 ScaleObjectiveValue(obj, best_solution_objective_value_);
275 const double user_bound =
276 ScaleObjectiveValue(obj, inner_objective_lower_bound_);
277 const double gap = std::abs(user_best - user_bound);
278 if (gap <= absolute_gap_limit_) {
279 SOLVER_LOG(logger_, "Absolute gap limit of ", absolute_gap_limit_,
280 " reached.");
281 UpdateBestStatus(CpSolverStatus::OPTIMAL);
282
283 // Note(user): Some code path in single-thread assumes that the problem
284 // can only be solved when they have proven infeasibility and do not check
285 // the ProblemIsSolved() method. So we force a stop here.
286 if (always_synchronize_) shared_time_limit_->Stop();
287 }
288 if (gap / std::max(1.0, std::abs(user_best)) < relative_gap_limit_) {
289 SOLVER_LOG(logger_, "Relative gap limit of ", relative_gap_limit_,
290 " reached.");
291 UpdateBestStatus(CpSolverStatus::OPTIMAL);
292
293 // Same as above.
294 if (always_synchronize_) shared_time_limit_->Stop();
295 }
296}
297
299 const std::string& update_info, IntegerValue lb, IntegerValue ub) {
300 absl::MutexLock mutex_lock(&mutex_);
301 CHECK(objective_or_null_ != nullptr);
302
303 // The problem is already solved!
304 //
305 // TODO(user): A thread might not be notified right away that the new bounds
306 // that it is pushing make the problem infeasible. Fix that. For now we just
307 // abort early here to avoid logging the "#Done" message multiple times.
308 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
309 return;
310 }
311
312 const bool ub_change = ub < inner_objective_upper_bound_;
313 const bool lb_change = lb > inner_objective_lower_bound_;
314 if (!lb_change && !ub_change) return;
315
316 if (lb_change) {
317 // When the improving problem is infeasible, it is possible to report
318 // arbitrary high inner_objective_lower_bound_. We make sure it never cross
319 // the current best solution, so that we always report globally valid lower
320 // bound.
321 DCHECK_LE(inner_objective_upper_bound_, best_solution_objective_value_);
322 inner_objective_lower_bound_ =
323 std::min(best_solution_objective_value_, lb.value());
324 }
325 if (ub_change) {
326 inner_objective_upper_bound_ = ub.value();
327 }
328
329 if (always_synchronize_) {
330 synchronized_inner_objective_lower_bound_ =
331 IntegerValue(inner_objective_lower_bound_);
332 synchronized_inner_objective_upper_bound_ =
333 IntegerValue(inner_objective_upper_bound_);
334 }
335
336 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
337 if (best_status_ == CpSolverStatus::FEASIBLE ||
338 best_status_ == CpSolverStatus::OPTIMAL) {
339 UpdateBestStatus(CpSolverStatus::OPTIMAL);
340 } else {
341 UpdateBestStatus(CpSolverStatus::INFEASIBLE);
342 }
343 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
344 SOLVER_LOG(logger_,
345 SatProgressMessage("Done", wall_timer_.Get(), update_info));
346 return;
347 }
348 if (logger_->LoggingIsEnabled() || !best_bound_callbacks_.empty()) {
349 const CpObjectiveProto& obj = *objective_or_null_;
350 const double best =
351 ScaleObjectiveValue(obj, best_solution_objective_value_);
352 double new_lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
353 if (lb_change) {
354 for (const auto& callback_entry : best_bound_callbacks_) {
355 callback_entry.second(new_lb);
356 }
357 }
358 if (logger_->LoggingIsEnabled()) {
359 double new_ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
360 if (obj.scaling_factor() < 0) {
361 std::swap(new_lb, new_ub);
362 }
363 RegisterObjectiveBoundImprovement(update_info);
364 logger_->ThrottledLog(bounds_logging_id_,
365 ProgressMessage("Bound", wall_timer_.Get(), best,
366 new_lb, new_ub, update_info));
367 }
368 }
369 TestGapLimitsIfNeeded();
370}
371
372// Invariant: the status always start at UNKNOWN and can only evolve as follow:
373// UNKNOWN -> FEASIBLE -> OPTIMAL
374// UNKNOWN -> INFEASIBLE
376 const std::string& worker_info) {
377 absl::MutexLock mutex_lock(&mutex_);
378 if (best_status_ == CpSolverStatus::FEASIBLE ||
379 best_status_ == CpSolverStatus::OPTIMAL) {
380 // We also use this status to indicate that we enumerated all solutions to
381 // a feasible problem.
382 UpdateBestStatus(CpSolverStatus::OPTIMAL);
383
384 // We just proved that the best solution cannot be improved uppon, so we
385 // have a new lower bound.
386 inner_objective_lower_bound_ = best_solution_objective_value_;
387 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
388 } else {
389 CHECK_EQ(num_solutions_, 0);
390 UpdateBestStatus(CpSolverStatus::INFEASIBLE);
391 }
392 SOLVER_LOG(logger_,
393 SatProgressMessage("Done", wall_timer_.Get(), worker_info));
394}
395
396void SharedResponseManager::AddUnsatCore(const std::vector<int>& core) {
397 absl::MutexLock mutex_lock(&mutex_);
398 unsat_cores_ = core;
399}
400
402 absl::MutexLock mutex_lock(&mutex_);
403 return synchronized_inner_objective_lower_bound_;
404}
405
407 absl::MutexLock mutex_lock(&mutex_);
408 return synchronized_inner_objective_upper_bound_;
409}
410
412 absl::MutexLock mutex_lock(&mutex_);
413 synchronized_inner_objective_lower_bound_ =
414 IntegerValue(inner_objective_lower_bound_);
415 synchronized_inner_objective_upper_bound_ =
416 IntegerValue(inner_objective_upper_bound_);
417 synchronized_best_status_ = best_status_;
418 if (solutions_.NumSolutions() > 0) {
419 first_solution_solvers_should_stop_ = true;
420 }
421 logger_->FlushPendingThrottledLogs();
422}
423
425 absl::MutexLock mutex_lock(&mutex_);
426 return IntegerValue(best_solution_objective_value_);
427}
428
430 absl::MutexLock mutex_lock(&mutex_);
431 return gap_integral_;
432}
433
435 std::function<void(std::vector<int64_t>*)> postprocessor) {
436 absl::MutexLock mutex_lock(&mutex_);
437 solution_postprocessors_.push_back(postprocessor);
438}
439
441 std::function<void(CpSolverResponse*)> postprocessor) {
442 absl::MutexLock mutex_lock(&mutex_);
443 postprocessors_.push_back(postprocessor);
444}
445
447 std::function<void(CpSolverResponse*)> postprocessor) {
448 absl::MutexLock mutex_lock(&mutex_);
449 final_postprocessors_.push_back(postprocessor);
450}
451
453 std::function<void(const CpSolverResponse&)> callback) {
454 absl::MutexLock mutex_lock(&mutex_);
455 const int id = next_callback_id_++;
456 callbacks_.emplace_back(id, std::move(callback));
457 return id;
458}
459
461 absl::MutexLock mutex_lock(&mutex_);
462 for (int i = 0; i < callbacks_.size(); ++i) {
463 if (callbacks_[i].first == callback_id) {
464 callbacks_.erase(callbacks_.begin() + i);
465 return;
466 }
467 }
468 LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
469}
470
472 std::function<std::string(const CpSolverResponse&)> callback) {
473 absl::MutexLock mutex_lock(&mutex_);
474 const int id = next_search_log_callback_id_++;
475 search_log_callbacks_.emplace_back(id, std::move(callback));
476 return id;
477}
478
480 absl::MutexLock mutex_lock(&mutex_);
481 for (int i = 0; i < search_log_callbacks_.size(); ++i) {
482 if (search_log_callbacks_[i].first == callback_id) {
483 search_log_callbacks_.erase(search_log_callbacks_.begin() + i);
484 return;
485 }
486 }
487 LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
488}
489
491 std::function<void(double)> callback) {
492 absl::MutexLock mutex_lock(&mutex_);
493 const int id = next_best_bound_callback_id_++;
494 best_bound_callbacks_.emplace_back(id, std::move(callback));
495 return id;
496}
497
499 absl::MutexLock mutex_lock(&mutex_);
500 for (int i = 0; i < best_bound_callbacks_.size(); ++i) {
501 if (best_bound_callbacks_[i].first == callback_id) {
502 best_bound_callbacks_.erase(best_bound_callbacks_.begin() + i);
503 return;
504 }
505 }
506 LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
507}
508
509CpSolverResponse SharedResponseManager::GetResponseInternal(
510 absl::Span<const int64_t> variable_values,
511 const std::string& solution_info) {
512 CpSolverResponse result;
513 result.set_status(best_status_);
514 if (!unsat_cores_.empty()) {
515 DCHECK_EQ(best_status_, CpSolverStatus::INFEASIBLE);
516 result.mutable_sufficient_assumptions_for_infeasibility()->Assign(
517 unsat_cores_.begin(), unsat_cores_.end());
518 }
519 FillObjectiveValuesInResponse(&result);
520 result.set_solution_info(solution_info);
521
522 // Tricky: We copy the solution now for the case where MergeFrom() belows
523 // override it!
524 //
525 // TODO(user): Fix. This is messy, we should really just override stats not
526 // important things like solution or status with the MergeFrom() below.
527 if (best_status_ == CpSolverStatus::FEASIBLE ||
528 best_status_ == CpSolverStatus::OPTIMAL) {
529 result.mutable_solution()->Assign(variable_values.begin(),
530 variable_values.end());
531 }
532
533 // Note that we allow subsolver_responses_ to override the fields set above.
534 // That is the status, solution_info and objective values...
535 if (!subsolver_responses_.empty()) {
536 result.MergeFrom(subsolver_responses_.front());
537 }
538
539 if (result.status() == CpSolverStatus::FEASIBLE ||
540 result.status() == CpSolverStatus::OPTIMAL) {
541 // We need to copy the solution before we postsolve it.
542 std::vector<int64_t> solution(result.solution().begin(),
543 result.solution().end());
544 for (int i = solution_postprocessors_.size(); --i >= 0;) {
545 solution_postprocessors_[i](&solution);
546 }
547 result.mutable_solution()->Assign(solution.begin(), solution.end());
548 }
549
550 // Apply response postprocessor to set things like timing information.
551 for (int i = postprocessors_.size(); --i >= 0;) {
552 postprocessors_[i](&result);
553 }
554 return result;
555}
556
558 absl::MutexLock mutex_lock(&mutex_);
559 CpSolverResponse result =
560 solutions_.NumSolutions() == 0
561 ? GetResponseInternal({}, "")
562 : GetResponseInternal(solutions_.GetSolution(0).variable_values,
563 solutions_.GetSolution(0).info);
564
565 // If this is true, we postsolve and copy all of our solutions.
566 if (parameters_.fill_additional_solutions_in_response()) {
567 std::vector<int64_t> temp;
568 for (int i = 0; i < solutions_.NumSolutions(); ++i) {
569 temp = solutions_.GetSolution(i).variable_values;
570 for (int i = solution_postprocessors_.size(); --i >= 0;) {
571 solution_postprocessors_[i](&temp);
572 }
573 result.add_additional_solutions()->mutable_values()->Assign(temp.begin(),
574 temp.end());
575 }
576 }
577
578 // final postprocessors will print out the final log. They must be called
579 // last.
580 for (int i = final_postprocessors_.size(); --i >= 0;) {
581 final_postprocessors_[i](&result);
582 }
583
584 return result;
585}
586
588 const CpSolverResponse& response) {
589 absl::MutexLock mutex_lock(&mutex_);
590 return subsolver_responses_.push_back(response);
591}
592
593void SharedResponseManager::FillObjectiveValuesInResponse(
594 CpSolverResponse* response) const {
595 if (objective_or_null_ == nullptr) return;
596 const CpObjectiveProto& obj = *objective_or_null_;
597
598 if (best_status_ == CpSolverStatus::INFEASIBLE) {
599 response->clear_objective_value();
600 response->clear_best_objective_bound();
601 response->clear_inner_objective_lower_bound();
602 return;
603 }
604
605 // Set the objective value.
606 // If we don't have any solution, we use our inner bound.
607 if (best_status_ == CpSolverStatus::UNKNOWN) {
608 response->set_objective_value(
609 ScaleObjectiveValue(obj, inner_objective_upper_bound_));
610 } else {
611 response->set_objective_value(
612 ScaleObjectiveValue(obj, best_solution_objective_value_));
613 }
614
615 // Update the best bound in the response.
616 response->set_inner_objective_lower_bound(
617 ScaleInnerObjectiveValue(obj, inner_objective_lower_bound_));
618 response->set_best_objective_bound(
619 ScaleObjectiveValue(obj, inner_objective_lower_bound_));
620
621 // Update the primal integral.
622 response->set_gap_integral(gap_integral_);
623}
624
626 absl::Span<const int64_t> solution_values, const std::string& solution_info,
627 Model* model) {
628 absl::MutexLock mutex_lock(&mutex_);
629
630 // For SAT problems, we add the solution to the solution pool for retrieval
631 // later.
632 if (objective_or_null_ == nullptr) {
634 solution.variable_values.assign(solution_values.begin(),
635 solution_values.end());
636 solution.info = solution_info;
637 solutions_.Add(solution);
638 } else {
639 const int64_t objective_value =
640 ComputeInnerObjective(*objective_or_null_, solution_values);
641
642 // Add this solution to the pool, even if it is not improving.
644 solution.variable_values.assign(solution_values.begin(),
645 solution_values.end());
647 solution.info = solution_info;
648 solutions_.Add(solution);
649
650 // Ignore any non-strictly improving solution.
651 if (objective_value > inner_objective_upper_bound_) return;
652
653 // Our inner_objective_lower_bound_ should be a globaly valid bound, until
654 // the problem become infeasible (i.e the lb > ub) in which case the bound
655 // is no longer globally valid. Here, because we have a strictly improving
656 // solution, we shouldn't be in the infeasible setting yet.
657 DCHECK_GE(objective_value, inner_objective_lower_bound_);
658
659 DCHECK_LT(objective_value, best_solution_objective_value_);
660 best_solution_objective_value_ = objective_value;
661
662 // Update the new bound.
663 inner_objective_upper_bound_ = objective_value - 1;
664 }
665
666 // In single thread, no one is synchronizing the solution manager, so we
667 // should do it from here.
668 if (always_synchronize_) {
669 solutions_.Synchronize();
670 first_solution_solvers_should_stop_ = true;
671 }
672
673 // Note that the objective will be filled by
674 // FillObjectiveValuesInResponse().
675 if (objective_or_null_ == nullptr && !parameters_.enumerate_all_solutions()) {
676 UpdateBestStatus(CpSolverStatus::OPTIMAL);
677 } else {
678 UpdateBestStatus(CpSolverStatus::FEASIBLE);
679 }
680
681 // Mark model as OPTIMAL if the inner bound crossed.
682 if (objective_or_null_ != nullptr &&
683 inner_objective_lower_bound_ > inner_objective_upper_bound_) {
684 UpdateBestStatus(CpSolverStatus::OPTIMAL);
685 }
686
687 // Logging.
688 ++num_solutions_;
689
690 // Compute the post-solved response once.
691 CpSolverResponse tmp_postsolved_response;
692 if ((!search_log_callbacks_.empty() && logger_->LoggingIsEnabled()) ||
693 !callbacks_.empty()) {
694 tmp_postsolved_response =
695 GetResponseInternal(solution_values, solution_info);
696 FillSolveStatsInResponse(model, &tmp_postsolved_response);
697 }
698
699 // TODO(user): Remove this code and the need for model in this function.
700 // Use search log callbacks instead.
701 if (logger_->LoggingIsEnabled()) {
702 std::string solution_message = solution_info;
703 if (model != nullptr) {
704 const int64_t num_bool = model->Get<Trail>()->NumVariables();
705 const int64_t num_fixed = model->Get<SatSolver>()->NumFixedVariables();
706 absl::StrAppend(&solution_message, " (fixed_bools=", num_fixed, "/",
707 num_bool, ")");
708 }
709
710 if (!search_log_callbacks_.empty()) {
711 for (const auto& pair : search_log_callbacks_) {
712 absl::StrAppend(&solution_message, " ",
713 pair.second(tmp_postsolved_response));
714 }
715 }
716
717 if (objective_or_null_ != nullptr) {
718 const CpObjectiveProto& obj = *objective_or_null_;
719 const double best =
720 ScaleObjectiveValue(obj, best_solution_objective_value_);
721 double lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
722 double ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
723 if (obj.scaling_factor() < 0) {
724 std::swap(lb, ub);
725 }
726 RegisterSolutionFound(solution_message, num_solutions_);
727 SOLVER_LOG(logger_, ProgressMessage(absl::StrCat(num_solutions_),
728 wall_timer_.Get(), best, lb, ub,
729 solution_message));
730 } else {
731 SOLVER_LOG(logger_,
732 SatProgressMessage(absl::StrCat(num_solutions_),
733 wall_timer_.Get(), solution_message));
734 }
735 }
736
737 // Call callbacks.
738 // Note that we cannot call function that try to get the mutex_ here.
739 TestGapLimitsIfNeeded();
740 for (const auto& pair : callbacks_) {
741 pair.second(tmp_postsolved_response);
742 }
743
744#if !defined(__PORTABLE_PLATFORM__)
745 // We protect solution dumping with log_updates as LNS subsolvers share
746 // another solution manager, and we do not want to dump those.
747 if (logger_->LoggingIsEnabled() &&
748 absl::GetFlag(FLAGS_cp_model_dump_solutions)) {
749 const std::string file =
750 absl::StrCat(dump_prefix_, "solution_", num_solutions_, ".pb.txt");
751 LOG(INFO) << "Dumping solution to '" << file << "'.";
752
753 // Note that here we only want to dump the non-post-solved solution.
754 // This is only used for debugging.
755 CpSolverResponse response;
756 response.mutable_solution()->Assign(solution_values.begin(),
757 solution_values.end());
758 CHECK_OK(file::SetTextProto(file, response, file::Defaults()));
759 }
760#endif // __PORTABLE_PLATFORM__
761}
762
764 absl::MutexLock mutex_lock(&mutex_);
765 return synchronized_best_status_ == CpSolverStatus::OPTIMAL ||
766 synchronized_best_status_ == CpSolverStatus::INFEASIBLE;
767}
768
769void SharedResponseManager::UpdateBestStatus(const CpSolverStatus& status) {
770 best_status_ = status;
771 if (always_synchronize_) {
772 synchronized_best_status_ = status;
773 }
774}
775
776std::string ExtractSubSolverName(const std::string& improvement_info) {
777 if (improvement_info.empty()) return "";
778
779 // We assume the subsolver name is always first.
780 for (int i = 0; i < improvement_info.size(); ++i) {
781 if (!std::isalnum(improvement_info[i]) && improvement_info[i] != '_') {
782 return improvement_info.substr(0, i);
783 }
784 }
785
786 return improvement_info;
787}
788
789void SharedResponseManager::RegisterSolutionFound(
790 const std::string& improvement_info, int solution_rank) {
791 if (improvement_info.empty()) return;
792 const std::string subsolver_name = ExtractSubSolverName(improvement_info);
793 primal_improvements_count_[subsolver_name]++;
794 primal_improvements_min_rank_.insert({subsolver_name, solution_rank});
795 primal_improvements_max_rank_[subsolver_name] = solution_rank;
796}
797
798void SharedResponseManager::RegisterObjectiveBoundImprovement(
799 const std::string& improvement_info) {
800 if (improvement_info.empty() || improvement_info == "initial domain") return;
801 dual_improvements_count_[ExtractSubSolverName(improvement_info)]++;
802}
803
805 absl::MutexLock mutex_lock(&mutex_);
806 if (!primal_improvements_count_.empty()) {
807 std::vector<std::vector<std::string>> table;
808 table.push_back(
809 {absl::StrCat("Solutions (", num_solutions_, ")"), "Num", "Rank"});
810 for (const auto& entry : primal_improvements_count_) {
811 const int min_rank = primal_improvements_min_rank_[entry.first];
812 const int max_rank = primal_improvements_max_rank_[entry.first];
813 table.push_back({FormatName(entry.first), FormatCounter(entry.second),
814 absl::StrCat("[", min_rank, ",", max_rank, "]")});
815 }
816 SOLVER_LOG(logger_, FormatTable(table));
817 }
818 if (!dual_improvements_count_.empty()) {
819 std::vector<std::vector<std::string>> table;
820 table.push_back({"Objective bounds", "Num"});
821 for (const auto& entry : dual_improvements_count_) {
822 table.push_back({FormatName(entry.first), FormatCounter(entry.second)});
823 }
824 SOLVER_LOG(logger_, FormatTable(table));
825 }
826}
827
828SharedBoundsManager::SharedBoundsManager(const CpModelProto& model_proto)
829 : num_variables_(model_proto.variables_size()),
830 model_proto_(model_proto),
831 lower_bounds_(num_variables_, std::numeric_limits<int64_t>::min()),
832 upper_bounds_(num_variables_, std::numeric_limits<int64_t>::max()),
833 synchronized_lower_bounds_(num_variables_,
834 std::numeric_limits<int64_t>::min()),
835 synchronized_upper_bounds_(num_variables_,
836 std::numeric_limits<int64_t>::max()) {
837 changed_variables_since_last_synchronize_.ClearAndResize(num_variables_);
838 for (int i = 0; i < num_variables_; ++i) {
839 lower_bounds_[i] = model_proto.variables(i).domain(0);
840 const int domain_size = model_proto.variables(i).domain_size();
841 upper_bounds_[i] = model_proto.variables(i).domain(domain_size - 1);
842 synchronized_lower_bounds_[i] = lower_bounds_[i];
843 synchronized_upper_bounds_[i] = upper_bounds_[i];
844 }
845}
846
848 const std::string& worker_name, const std::vector<int>& variables,
849 const std::vector<int64_t>& new_lower_bounds,
850 const std::vector<int64_t>& new_upper_bounds) {
851 CHECK_EQ(variables.size(), new_lower_bounds.size());
852 CHECK_EQ(variables.size(), new_upper_bounds.size());
853 int num_improvements = 0;
854
855 absl::MutexLock mutex_lock(&mutex_);
856 for (int i = 0; i < variables.size(); ++i) {
857 const int var = variables[i];
858 if (var >= num_variables_) continue;
859 const int64_t old_lb = lower_bounds_[var];
860 const int64_t old_ub = upper_bounds_[var];
861 const int64_t new_lb = new_lower_bounds[i];
862 const int64_t new_ub = new_upper_bounds[i];
863 const bool changed_lb = new_lb > old_lb;
864 const bool changed_ub = new_ub < old_ub;
865 if (!changed_lb && !changed_ub) continue;
866
867 VLOG(3) << worker_name << " var=" << var << " [" << old_lb << "," << old_ub
868 << "] -> [" << new_lb << "," << new_ub << "]";
869
870 if (changed_lb) {
871 if (DEBUG_MODE && !debug_solution_.empty()) {
872 CHECK_LE(new_lb, debug_solution_[var]) << worker_name << " var=" << var;
873 }
874 lower_bounds_[var] = new_lb;
875 }
876 if (changed_ub) {
877 if (DEBUG_MODE && !debug_solution_.empty()) {
878 CHECK_GE(new_ub, debug_solution_[var]) << worker_name << " var=" << var;
879 }
880 upper_bounds_[var] = new_ub;
881 }
882 changed_variables_since_last_synchronize_.Set(var);
883 num_improvements++;
884 }
885 if (num_improvements > 0) {
886 total_num_improvements_ += num_improvements;
887 VLOG(3) << total_num_improvements_ << "/" << num_variables_;
888 bounds_exported_[worker_name] += num_improvements;
889 if (absl::GetFlag(FLAGS_cp_model_dump_tightened_models)) {
890 CpModelProto tight_model = model_proto_;
891 for (int i = 0; i < num_variables_; ++i) {
892 IntegerVariableProto* var_proto = tight_model.mutable_variables(i);
893 const Domain domain =
894 ReadDomainFromProto(*var_proto)
895 .IntersectionWith(Domain(lower_bounds_[i], upper_bounds_[i]));
896 FillDomainInProto(domain, var_proto);
897 }
898 const std::string filename = absl::StrCat(dump_prefix_, "tighened_model_",
899 export_counter_, ".pb.txt");
900 LOG(INFO) << "Dumping tightened model proto to '" << filename << "'.";
901 export_counter_++;
902 CHECK(WriteModelProtoToFile(tight_model, filename));
903 }
904 }
905}
906
907// TODO(user): Because we look at the non-synchronized and up to date bounds,
908// this break determinism if two solution for the same subpart comes at the same
909// time.
911 const std::vector<int64_t>& solution,
912 const std::vector<int>& variables_to_fix) {
913 absl::MutexLock mutex_lock(&mutex_);
914
915 // Abort if incompatible. Note that we only check the position that we are
916 // about to fix. This should be enough. Otherwise we might never accept any
917 // solution because the base LNS solution was not the same in some of the
918 // variables that we fixed here.
919 for (const int var : variables_to_fix) {
920 const int64_t value = solution[var];
921 if (value < lower_bounds_[var] || value > upper_bounds_[var]) {
922 VLOG(1) << "Incompatibility in FixVariablesFromPartialSolution() "
923 << "var: " << var << " value: " << value << " bounds: ["
924 << lower_bounds_[var] << "," << upper_bounds_[var] << "]";
925 return;
926 }
927 }
928
929 // Fix the variables.
930 for (const int var : variables_to_fix) {
931 const int64_t old_lb = lower_bounds_[var];
932 const int64_t old_ub = upper_bounds_[var];
933 const bool changed_lb = solution[var] > old_lb;
934 const bool changed_ub = solution[var] < old_ub;
935 if (!changed_lb && !changed_ub) continue;
936
937 lower_bounds_[var] = solution[var];
938 upper_bounds_[var] = solution[var];
939 changed_variables_since_last_synchronize_.Set(var);
940
941 // This is problematic as we might find a different partial solution.
942 // To allow for further investigation, we currently fix it to the debug
943 // solution instead.
944 if (DEBUG_MODE && !debug_solution_.empty()) {
945 if (solution[var] != debug_solution_[var]) {
946 LOG(INFO) << "Fixing to a different solution for var=" << var
947 << " debug=" << debug_solution_[var]
948 << " partial=" << solution[var];
949 lower_bounds_[var] = debug_solution_[var];
950 upper_bounds_[var] = debug_solution_[var];
951 }
952 }
953 }
954}
955
957 absl::MutexLock mutex_lock(&mutex_);
958 for (const int var :
959 changed_variables_since_last_synchronize_.PositionsSetAtLeastOnce()) {
960 synchronized_lower_bounds_[var] = lower_bounds_[var];
961 synchronized_upper_bounds_[var] = upper_bounds_[var];
962 for (int j = 0; j < id_to_changed_variables_.size(); ++j) {
963 id_to_changed_variables_[j].Set(var);
964 }
965 }
966 changed_variables_since_last_synchronize_.ClearAll();
967}
968
970 absl::MutexLock mutex_lock(&mutex_);
971 const int id = id_to_changed_variables_.size();
972 id_to_changed_variables_.resize(id + 1);
973 id_to_changed_variables_[id].ClearAndResize(num_variables_);
974 for (int var = 0; var < num_variables_; ++var) {
975 const int64_t lb = model_proto_.variables(var).domain(0);
976 const int domain_size = model_proto_.variables(var).domain_size();
977 const int64_t ub = model_proto_.variables(var).domain(domain_size - 1);
978 if (lb != synchronized_lower_bounds_[var] ||
979 ub != synchronized_upper_bounds_[var]) {
980 id_to_changed_variables_[id].Set(var);
981 }
982 }
983 return id;
984}
985
987 int id, std::vector<int>* variables, std::vector<int64_t>* new_lower_bounds,
988 std::vector<int64_t>* new_upper_bounds) {
989 variables->clear();
990 new_lower_bounds->clear();
991 new_upper_bounds->clear();
992
993 absl::MutexLock mutex_lock(&mutex_);
994 for (const int var : id_to_changed_variables_[id].PositionsSetAtLeastOnce()) {
995 variables->push_back(var);
996 }
997 id_to_changed_variables_[id].ClearAll();
998
999 // We need to report the bounds in a deterministic order as it is difficult to
1000 // guarantee that nothing depend on the order in which the new bounds are
1001 // processed.
1002 absl::c_sort(*variables);
1003 for (const int var : *variables) {
1004 new_lower_bounds->push_back(synchronized_lower_bounds_[var]);
1005 new_upper_bounds->push_back(synchronized_upper_bounds_[var]);
1006 }
1007}
1008
1009void SharedBoundsManager::UpdateDomains(std::vector<Domain>* domains) {
1010 absl::MutexLock mutex_lock(&mutex_);
1011 CHECK_EQ(domains->size(), synchronized_lower_bounds_.size());
1012 for (int var = 0; var < domains->size(); ++var) {
1013 (*domains)[var] = (*domains)[var].IntersectionWith(Domain(
1014 synchronized_lower_bounds_[var], synchronized_upper_bounds_[var]));
1015 }
1016}
1017
1019 absl::MutexLock mutex_lock(&mutex_);
1020 if (!bounds_exported_.empty()) {
1021 std::vector<std::vector<std::string>> table;
1022 table.push_back({"Improving bounds shared", "Num"});
1023 for (const auto& entry : bounds_exported_) {
1024 table.push_back({FormatName(entry.first), FormatCounter(entry.second)});
1025 }
1026 SOLVER_LOG(logger, FormatTable(table));
1027 }
1028}
1029
1030int SharedBoundsManager::NumBoundsExported(const std::string& worker_name) {
1031 absl::MutexLock mutex_lock(&mutex_);
1032 const auto it = bounds_exported_.find(worker_name);
1033 if (it == bounds_exported_.end()) return 0;
1034 return it->second;
1035}
1036
1038 for (auto& buffer : clauses_by_size_) {
1039 buffer.reserve(kMaxBufferedLiterals);
1040 }
1041}
1042
1043bool UniqueClauseStream::Add(absl::Span<const int> clause) {
1044 absl::MutexLock mutex_lock(&mutex_);
1045 if (clause.size() > kMaxClauseSize || clause.size() <= 2) return false;
1046 // This is just a safety check, the caller should have called CanAccept().
1047 if (NumLiteralsOfSize(clause.size()) + clause.size() > kMaxBufferedLiterals) {
1048 return false;
1049 }
1050 if (BlockClause(clause)) {
1051 std::vector<int>* buffer = MutableBufferForSize(clause.size());
1052 buffer->insert(buffer->end(), clause.begin(), clause.end());
1053 return true;
1054 }
1055 return false;
1056}
1057
1058bool UniqueClauseStream::BlockClause(absl::Span<const int> clause) {
1059 if (clause.size() > kMaxClauseSize) return false;
1060 if (clause.size() <= 2) return false;
1061 return fingerprints_.emplace(HashClause(clause)).second;
1062}
1063
1064bool UniqueClauseStream::Delete(absl::Span<const int> clause) {
1065 const size_t fingerprint = HashClause(clause);
1066 absl::MutexLock mutex_lock(&mutex_);
1067 // Note a clause with this hash may be buffered, but not yet exported.
1068 return fingerprints_.erase(fingerprint) == 1;
1069}
1070
1074 int to_fill = kMaxLiteralsPerBatch;
1075 absl::MutexLock mutex_lock(&mutex_);
1076 for (int size = kMinClauseSize; size <= kMaxClauseSize; ++size) {
1077 CHECK_EQ(NumLiteralsOfSize(size) % size, 0);
1078 while (to_fill >= size && NumLiteralsOfSize(size) > 0) {
1079 absl::Span<const int> clause = NextClause(size);
1080 if (fingerprints_.contains(HashClause(clause))) {
1081 buffer.Add(NextClause(size));
1082 to_fill -= size;
1083 }
1084 PopClause(size);
1085 }
1086 }
1087 return buffer;
1088}
1089
1091 int size,
1092 int max_clauses_to_export) {
1093 int num_exported_clauses = 0;
1094 absl::MutexLock mutex_lock(&mutex_);
1095 while (NumLiteralsOfSize(size) > 0 &&
1096 num_exported_clauses < max_clauses_to_export) {
1097 absl::Span<const int> clause = NextClause(size);
1098 // Don't emit deleted clauses.
1099 if (fingerprints_.contains(HashClause(clause)) && upstream.Add(clause)) {
1100 ++num_exported_clauses;
1101 }
1102 PopClause(size);
1103 }
1104 return num_exported_clauses;
1105}
1106
1108 absl::MutexLock mutex_lock(&mutex_);
1109 int result = 0;
1110 for (const auto& buffer : clauses_by_size_) {
1111 result += buffer.size();
1112 }
1113 return result;
1114}
1115
1116bool UniqueClauseStream::CanAccept(int size, int lbd) const {
1117 if (size <= 2 || size > kMaxClauseSize) return false;
1118 absl::MutexLock mutex_lock(&mutex_);
1119 if (lbd > lbd_threshold_) return false;
1120 int num_literals_up_to_size = 0;
1121 for (int i = kMinClauseSize; i <= size; ++i) {
1122 num_literals_up_to_size += NumLiteralsOfSize(i);
1123 }
1124 return num_literals_up_to_size + size <= kMaxBufferedLiterals;
1125}
1126
1128 absl::MutexLock mutex_lock(&mutex_);
1129 int literals_to_remove = 0;
1130 for (const auto& buffer : clauses_by_size_) {
1131 literals_to_remove += buffer.size();
1132 }
1133 literals_to_remove -= kMaxBufferedLiterals;
1134 for (int size = kMaxClauseSize; size >= kMinClauseSize; --size) {
1135 while (NumLiteralsOfSize(size) > 0) {
1136 // Stop if removing one more clause of the current size would
1137 // leave the buffer under full. Otherwise we might remove a shorter
1138 // clause later!
1139 if (literals_to_remove < size) return;
1140 fingerprints_.erase(HashClause(NextClause(size)));
1141 PopClause(size);
1142 literals_to_remove -= size;
1143 }
1144 }
1145}
1146
1148 absl::MutexLock mutex_lock(&mutex_);
1149 lbd_threshold_ = lbd;
1150}
1151
1152size_t UniqueClauseStream::HashClause(absl::Span<const int> clause,
1153 size_t hash_seed) {
1154 size_t hash = absl::HashOf(hash_seed, clause.size());
1155 for (int i = 0; i < clause.size(); ++i) {
1156 hash ^= absl::HashOf(clause[i], hash_seed);
1157 }
1158 return hash;
1159}
1160
1161absl::Span<const int> UniqueClauseStream::NextClause(int size) const {
1162 absl::Span<const int> buffer = BufferForSize(size);
1163 return buffer.subspan(buffer.size() - size, size);
1164}
1165
1166void UniqueClauseStream::PopClause(int size) {
1167 std::vector<int>* buffer = MutableBufferForSize(size);
1168 buffer->erase(buffer->end() - size, buffer->end());
1169}
1170
1171int UniqueClauseStream::NumClausesOfSize(int size) const {
1172 return NumLiteralsOfSize(size) / size;
1173}
1174
1175int UniqueClauseStream::NumLiteralsOfSize(int size) const {
1176 return BufferForSize(size).size();
1177}
1178
1180 absl::Duration share_frequency)
1181 : always_synchronize_(always_synchronize),
1182 share_frequency_(share_frequency) {}
1183
1185 absl::MutexLock mutex_lock(&mutex_);
1186 const int id = id_to_last_processed_binary_clause_.size();
1187 id_to_last_processed_binary_clause_.resize(id + 1, 0);
1188 id_to_last_returned_batch_.resize(id + 1, 0);
1189 id_to_last_finished_batch_.resize(id + 1, 0);
1190 id_to_clauses_exported_.resize(id + 1, 0);
1191 id_to_clause_stream_.emplace_back();
1192 return id;
1193}
1194
1196 const std::string& worker_name) {
1197 absl::MutexLock mutex_lock(&mutex_);
1198 id_to_worker_name_[id] = worker_name;
1199}
1200
1201void SharedClausesManager::AddBinaryClause(int id, int lit1, int lit2) {
1202 if (lit2 < lit1) std::swap(lit1, lit2);
1203 const auto p = std::make_pair(lit1, lit2);
1204
1205 absl::MutexLock mutex_lock(&mutex_);
1206 const auto [unused_it, inserted] = added_binary_clauses_set_.insert(p);
1207 if (inserted) {
1208 added_binary_clauses_.push_back(p);
1209 if (always_synchronize_) ++last_visible_binary_clause_;
1210 id_to_clauses_exported_[id]++;
1211
1212 // Small optim. If the worker is already up to date with clauses to import,
1213 // we can mark this new clause as already seen.
1214 if (id_to_last_processed_binary_clause_[id] ==
1215 added_binary_clauses_.size() - 1) {
1216 id_to_last_processed_binary_clause_[id]++;
1217 }
1218 }
1219}
1220
1221std::vector<absl::Span<const int>> SharedClausesManager::GetUnseenClauses(
1222 int id) {
1223 std::vector<absl::Span<const int>> result;
1224 absl::MutexLock mutex_lock(&mutex_);
1225 for (int i = id_to_last_returned_batch_[id]; i < batches_.size(); ++i) {
1226 for (int j = 0; j < batches_[i].size(); ++j) {
1227 result.push_back(batches_[i][j]);
1228 }
1229 }
1230 id_to_last_finished_batch_[id] = id_to_last_returned_batch_[id];
1231 id_to_last_returned_batch_[id] = batches_.size();
1232 return result;
1233}
1234
1236 int id, std::vector<std::pair<int, int>>* new_clauses) {
1237 new_clauses->clear();
1238 absl::MutexLock mutex_lock(&mutex_);
1239 const int last_binary_clause_seen = id_to_last_processed_binary_clause_[id];
1240 if (last_binary_clause_seen >= last_visible_binary_clause_) return;
1241
1242 new_clauses->assign(
1243 added_binary_clauses_.begin() + last_binary_clause_seen,
1244 added_binary_clauses_.begin() + last_visible_binary_clause_);
1245 id_to_last_processed_binary_clause_[id] = last_visible_binary_clause_;
1246}
1247
1249 absl::MutexLock mutex_lock(&mutex_);
1250 absl::btree_map<std::string, int64_t> name_to_clauses;
1251 for (int id = 0; id < id_to_clauses_exported_.size(); ++id) {
1252 if (id_to_clauses_exported_[id] == 0) continue;
1253 name_to_clauses[id_to_worker_name_[id]] = id_to_clauses_exported_[id];
1254 }
1255 if (!name_to_clauses.empty()) {
1256 std::vector<std::vector<std::string>> table;
1257 table.push_back({"Clauses shared", "Num"});
1258 for (const auto& entry : name_to_clauses) {
1259 table.push_back({FormatName(entry.first), FormatCounter(entry.second)});
1260 }
1261 SOLVER_LOG(logger, FormatTable(table));
1262 }
1263}
1264
1266 absl::MutexLock mutex_lock(&mutex_);
1267 last_visible_binary_clause_ = added_binary_clauses_.size();
1268 const int num_workers = id_to_clause_stream_.size();
1269 if (num_workers <= 1) return;
1270 if (!share_timer_.IsRunning()) share_timer_.Start();
1271 if (share_timer_.GetDuration() < share_frequency_) return;
1272 share_timer_.Restart();
1273
1274 // Tune LBD threshold for individual workers based on how the worker's buffer
1275 // is. We aim to ensure workers can always export their fair share of clauses.
1276 for (int id = 0; id < num_workers; ++id) {
1277 UniqueClauseStream& stream = id_to_clause_stream_[id];
1278 const int lbd_threshold = stream.lbd_threshold();
1279 const int num_buffered_literals = stream.NumBufferedLiterals();
1280 const bool underfull =
1281 num_buffered_literals <
1283 const bool overfull =
1284 num_buffered_literals >
1286 const int new_lbd = std::clamp(lbd_threshold + underfull - overfull, 2,
1288 if (new_lbd != lbd_threshold) {
1289 VLOG(2) << id_to_worker_name_[id]
1290 << " sharing clauses with lbd <= " << new_lbd;
1291 stream.set_lbd_threshold(new_lbd);
1292 }
1293 }
1294
1295 std::vector<int> ids(num_workers);
1296 int literals_to_fill = UniqueClauseStream::kMaxLiteralsPerBatch;
1299 ids.clear();
1300 for (int id = 0; id < num_workers; ++id) {
1301 if (id_to_clause_stream_[id].NumBufferedLiteralsOfSize(size) > 0) {
1302 ids.push_back(id);
1303 }
1304 }
1305 // Use progressive filling to attempt to fill the batch with clauses of
1306 // minimum size, this is max-min fair.
1307 while (!ids.empty()) {
1308 const int clauses_to_fill = literals_to_fill / size;
1309 if (clauses_to_fill == 0) break;
1310 // Some workers need to export more clauses to fill the batch due to
1311 // rounding, but we don't want all workers to round up.
1312 const int num_to_round_up = clauses_to_fill % ids.size();
1313 for (int i = 0; i < ids.size(); ++i) {
1314 const bool round_up = i < num_to_round_up;
1315 const int id = ids[i];
1316 const int shared = id_to_clause_stream_[id].FillUpstreamBuffer(
1317 all_clauses_, size, clauses_to_fill / ids.size() + round_up);
1318 id_to_clauses_exported_[id] += shared;
1319 if (shared == 0 ||
1320 id_to_clause_stream_[id].NumBufferedLiteralsOfSize(size) == 0) {
1321 ids[i] = ids.back();
1322 ids.pop_back();
1323 --i;
1324 }
1325 }
1326 }
1327 }
1328 if (all_clauses_.NumBufferedLiterals() > 0) {
1329 batches_.push_back(all_clauses_.NextBatch());
1330 VLOG(2) << "Batch #" << batches_.size() << " w/ " << batches_.back().size()
1331 << " clauses max size = "
1332 << batches_.back()[batches_.back().size() - 1].size();
1333 }
1334 // Delete batches that have been consumed by all workers.
1335 // Keep a few batches around for startup (min finished batch doesn't count
1336 // workers that haven't registered yet).
1337 // This also ensures that our fingerprint table always contains the last few
1338 // batches, so we reduce the chance of an old buffered duplicate clause on
1339 // a worker being emitted from the global stream multiple times.
1340 if (batches_.size() < kMinBatches) return;
1341 const int min_finished_batch =
1342 std::min<int>(batches_.size() - kMinBatches,
1343 *absl::c_min_element(id_to_last_finished_batch_));
1344 for (int i = 0; i < min_finished_batch; ++i) {
1345 VLOG(2) << "Erasing batch";
1346 for (int i = 0; i < batches_.front().size(); ++i) {
1347 all_clauses_.Delete(batches_.front()[i]);
1348 }
1349 batches_.pop_front();
1350 }
1351 for (int id = 0; id < id_to_last_finished_batch_.size(); ++id) {
1352 id_to_last_returned_batch_[id] -= min_finished_batch;
1353 id_to_last_finished_batch_[id] -= min_finished_batch;
1354 }
1355 // TODO(user): We could cleanup binary clauses that have been consumed.
1356}
1357
1359 absl::Span<const std::pair<std::string, int64_t>> stats) {
1360 absl::MutexLock mutex_lock(&mutex_);
1361 for (const auto& [key, count] : stats) {
1362 stats_[key] += count;
1363 }
1364}
1365
1367 absl::MutexLock mutex_lock(&mutex_);
1368 if (stats_.empty()) return;
1369
1370 SOLVER_LOG(logger, "Stats across workers (summed):");
1371 std::vector<std::pair<std::string, int64_t>> to_sort_;
1372 for (const auto& [key, count] : stats_) {
1373 to_sort_.push_back({key, count});
1374 }
1375 std::sort(to_sort_.begin(), to_sort_.end());
1376 for (const auto& [key, count] : to_sort_) {
1377 SOLVER_LOG(logger, " ", key, ": ", FormatCounter(count));
1378 }
1379 SOLVER_LOG(logger, "");
1380}
1381
1382} // namespace sat
1383} // namespace operations_research
IntegerValue size
int64_t max
int max_rank
int min_rank
int64_t min
double Get() const
Definition timer.h:46
Domain IntersectionWith(const Domain &domain) const
void reserve(int size)
Reserve memory if it is already known or tightly estimated.
Definition util.h:86
int Add(absl::Span< const V > values)
Definition util.h:739
The model "singleton" shared time limit.
Definition util.h:331
void UpdateDomains(std::vector< Domain > *domains)
int NumBoundsExported(const std::string &worker_name)
void FixVariablesFromPartialSolution(const std::vector< int64_t > &solution, const std::vector< 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)
void ReportPotentialNewBounds(const std::string &worker_name, const std::vector< int > &variables, const std::vector< int64_t > &new_lower_bounds, const std::vector< int64_t > &new_upper_bounds)
int RegisterNewId()
Ids are used to identify which worker is exporting/importing clauses.
void AddBinaryClause(int id, int lit1, int lit2)
void LogStatistics(SolverLogger *logger)
Search statistics.
void SetWorkerNameForId(int id, const std::string &worker_name)
void GetUnseenBinaryClauses(int id, std::vector< std::pair< int, int > > *new_clauses)
std::vector< absl::Span< const int > > GetUnseenClauses(int id)
SharedClausesManager(bool always_synchronize, absl::Duration share_frequency)
std::vector< double > PopLast()
If there are no solution, this return an empty vector.
void AddSolution(const std::vector< double > &lp_solution)
void NewLPSolution(std::vector< double > lp_solution)
void AppendResponseToBeMerged(const CpSolverResponse &response)
void AddUnsatCore(const std::vector< int > &core)
void LogMessageWithThrottling(const std::string &prefix, const std::string &message)
void LogMessage(absl::string_view prefix, absl::string_view message)
Wrapper around our SolverLogger, but protected by mutex.
void SetSynchronizationMode(bool always_synchronize)
int AddLogCallback(std::function< std::string(const CpSolverResponse &)> callback)
int AddBestBoundCallback(std::function< void(double)> callback)
void DisplayImprovementStatistics()
Display improvement stats.
void InitializeObjective(const CpModelProto &cp_model)
void SetGapLimitsFromParameters(const SatParameters &parameters)
void AddFinalResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
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 NewSolution(absl::Span< const int64_t > solution_values, const std::string &solution_info, Model *model=nullptr)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
Updates the inner objective bounds.
Solution GetSolution(int index) const
Returns the solution i where i must be smaller than NumSolutions().
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
Adds a bunch of stats, adding count for the same key together.
void Log(SolverLogger *logger)
Logs all the added stats.
int FillUpstreamBuffer(UniqueClauseStream &upstream, int clause_size, int max_clauses_to_export) ABSL_LOCKS_EXCLUDED(mutex_)
int NumBufferedLiterals() const ABSL_LOCKS_EXCLUDED(mutex_)
static size_t HashClause(absl::Span< const int > clause, size_t hash_seed=0)
Computes a hash that is independent of the order of literals in the clause.
void set_lbd_threshold(int lbd) ABSL_LOCKS_EXCLUDED(mutex_)
bool Delete(absl::Span< const int > clause) ABSL_LOCKS_EXCLUDED(mutex_)
bool Add(absl::Span< const int > clause) ABSL_LOCKS_EXCLUDED(mutex_)
CompactVectorVector< int > NextBatch() ABSL_LOCKS_EXCLUDED(mutex_)
int lbd_threshold() const ABSL_LOCKS_EXCLUDED(mutex_)
static constexpr int kMaxLiteralsPerBatch
Export 4KiB of clauses per batch.
SatParameters parameters
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
MPCallback * callback
const bool DEBUG_MODE
Definition macros.h:24
int64_t hash
double solution
Definition file.cc:169
absl::Status SetTextProto(absl::string_view filename, const google::protobuf::Message &proto, Options options)
Definition file.cc:337
Options Defaults()
Definition file.h:109
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, absl::Span< const int64_t > solution)
void FillSolveStatsInResponse(Model *model, CpSolverResponse *response)
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
Scales back a objective value to a double value from the original model.
std::string ExtractSubSolverName(const std::string &improvement_info)
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
Definition util.cc:49
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
int64_t ScaleInnerObjectiveValue(const CpObjectiveProto &proto, int64_t value)
Similar to ScaleObjectiveValue() but uses the integer version.
std::string FormatName(absl::string_view name)
This is used to format our table first row entry.
Definition util.h:196
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
Definition util.cc:77
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
std::vector< std::function< void(CpSolverResponse *)> > callbacks
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\"'.")
std::string message
Definition trace.cc:397
double objective_value
The value objective_vector^T * (solution - center_point).
#define SOLVER_LOG(logger,...)
Definition logging.h:109