32#include "absl/hash/hash.h"
33#include "absl/time/time.h"
36#if !defined(__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"
56#include "ortools/sat/sat_parameters.pb.h"
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\"'.");
76 std::vector<double> lp_solution) {
77 if (lp_solution.empty())
return;
84 absl::MutexLock mutex_lock(&
mutex_);
85 solution.rank = -num_synchronization_;
91 const std::vector<double>& lp_solution) {
92 absl::MutexLock mutex_lock(&mutex_);
94 solutions_.push_back(lp_solution);
95 if (solutions_.size() > 100) solutions_.pop_front();
99 absl::MutexLock mutex_lock(&mutex_);
100 return !solutions_.empty();
104 absl::MutexLock mutex_lock(&mutex_);
105 if (solutions_.empty())
return {};
108 std::vector<double>
solution = std::move(solutions_.back());
109 solutions_.pop_back();
114 : parameters_(*
model->GetOrCreate<SatParameters>()),
117 solutions_(parameters_.solution_pool_size(),
"feasible solutions"),
119 bounds_logging_id_ = logger_->GetNewThrottledId();
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)
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);
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);
146 if (
model ==
nullptr)
return;
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());
155 response->set_num_integers(
156 integer_trail ==
nullptr
158 : integer_trail->NumIntegerVariables().value() / 2);
159 response->set_num_integer_propagations(
160 integer_trail ==
nullptr ? 0 : integer_trail->num_enqueues());
164 response->set_num_lp_iterations(0);
165 for (
const auto& set_stats :
173 absl::MutexLock mutex_lock(&mutex_);
174 SOLVER_LOG(logger_, absl::StrFormat(
"#%-5s %6.2fs %s", prefix,
179 const std::string& prefix,
const std::string&
message) {
180 absl::MutexLock mutex_lock(&mutex_);
183 auto it = throttling_ids_.find(prefix);
184 if (it == throttling_ids_.end()) {
185 id = throttling_ids_[prefix] = logger_->GetNewThrottledId();
189 logger_->ThrottledLog(
id, absl::StrFormat(
"#%-5s %6.2fs %s", prefix,
194 absl::MutexLock mutex_lock(&mutex_);
196 return logger_->LoggingIsEnabled();
200 if (cp_model.has_objective()) {
201 objective_or_null_ = &cp_model.objective();
205 IntegerValue(domain.
Max()));
208 objective_or_null_ =
nullptr;
213 absl::MutexLock mutex_lock(&mutex_);
214 always_synchronize_ = always_synchronize;
218 absl::MutexLock mutex_lock(&mutex_);
219 update_integral_on_each_change_ = set;
223 absl::MutexLock mutex_lock(&mutex_);
224 UpdateGapIntegralInternal();
227void SharedResponseManager::UpdateGapIntegralInternal() {
228 if (objective_or_null_ ==
nullptr)
return;
231 const double time_delta = current_time - last_gap_integral_time_stamp_;
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;
245 last_gap_integral_time_stamp_ = current_time;
247 std::max(0.0,
static_cast<double>(inner_objective_upper_bound_) -
248 static_cast<double>(inner_objective_lower_bound_));
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();
259void SharedResponseManager::TestGapLimitsIfNeeded() {
263 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
267 if (absolute_gap_limit_ == 0 && relative_gap_limit_ == 0)
return;
270 if (inner_objective_lower_bound_ > inner_objective_upper_bound_)
return;
272 const CpObjectiveProto& obj = *objective_or_null_;
273 const double user_best =
275 const double user_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_,
281 UpdateBestStatus(CpSolverStatus::OPTIMAL);
286 if (always_synchronize_) shared_time_limit_->
Stop();
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_,
291 UpdateBestStatus(CpSolverStatus::OPTIMAL);
294 if (always_synchronize_) shared_time_limit_->
Stop();
299 const std::string& update_info, IntegerValue lb, IntegerValue ub) {
300 absl::MutexLock mutex_lock(&mutex_);
301 CHECK(objective_or_null_ !=
nullptr);
308 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
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;
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());
326 inner_objective_upper_bound_ = ub.value();
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_);
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);
341 UpdateBestStatus(CpSolverStatus::INFEASIBLE);
343 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
345 SatProgressMessage(
"Done", wall_timer_.
Get(), update_info));
348 if (logger_->LoggingIsEnabled() || !best_bound_callbacks_.empty()) {
349 const CpObjectiveProto& obj = *objective_or_null_;
354 for (
const auto& callback_entry : best_bound_callbacks_) {
355 callback_entry.second(new_lb);
358 if (logger_->LoggingIsEnabled()) {
360 if (obj.scaling_factor() < 0) {
361 std::swap(new_lb, new_ub);
363 RegisterObjectiveBoundImprovement(update_info);
364 logger_->ThrottledLog(bounds_logging_id_,
365 ProgressMessage(
"Bound", wall_timer_.
Get(), best,
366 new_lb, new_ub, update_info));
369 TestGapLimitsIfNeeded();
376 const std::string& worker_info) {
377 absl::MutexLock mutex_lock(&mutex_);
378 if (best_status_ == CpSolverStatus::FEASIBLE ||
379 best_status_ == CpSolverStatus::OPTIMAL) {
382 UpdateBestStatus(CpSolverStatus::OPTIMAL);
386 inner_objective_lower_bound_ = best_solution_objective_value_;
387 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
389 CHECK_EQ(num_solutions_, 0);
390 UpdateBestStatus(CpSolverStatus::INFEASIBLE);
393 SatProgressMessage(
"Done", wall_timer_.
Get(), worker_info));
397 absl::MutexLock mutex_lock(&mutex_);
402 absl::MutexLock mutex_lock(&mutex_);
403 return synchronized_inner_objective_lower_bound_;
407 absl::MutexLock mutex_lock(&mutex_);
408 return synchronized_inner_objective_upper_bound_;
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_;
419 first_solution_solvers_should_stop_ =
true;
421 logger_->FlushPendingThrottledLogs();
425 absl::MutexLock mutex_lock(&mutex_);
426 return IntegerValue(best_solution_objective_value_);
430 absl::MutexLock mutex_lock(&mutex_);
431 return gap_integral_;
435 std::function<
void(std::vector<int64_t>*)> postprocessor) {
436 absl::MutexLock mutex_lock(&mutex_);
437 solution_postprocessors_.push_back(postprocessor);
441 std::function<
void(CpSolverResponse*)> postprocessor) {
442 absl::MutexLock mutex_lock(&mutex_);
443 postprocessors_.push_back(postprocessor);
447 std::function<
void(CpSolverResponse*)> postprocessor) {
448 absl::MutexLock mutex_lock(&mutex_);
449 final_postprocessors_.push_back(postprocessor);
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));
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);
468 LOG(DFATAL) <<
"Callback id " << callback_id <<
" not registered.";
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));
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);
487 LOG(DFATAL) <<
"Callback id " << callback_id <<
" not registered.";
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));
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);
506 LOG(DFATAL) <<
"Callback id " << callback_id <<
" not registered.";
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());
519 FillObjectiveValuesInResponse(&result);
520 result.set_solution_info(solution_info);
527 if (best_status_ == CpSolverStatus::FEASIBLE ||
528 best_status_ == CpSolverStatus::OPTIMAL) {
529 result.mutable_solution()->Assign(variable_values.begin(),
530 variable_values.end());
535 if (!subsolver_responses_.empty()) {
536 result.MergeFrom(subsolver_responses_.front());
539 if (result.status() == CpSolverStatus::FEASIBLE ||
540 result.status() == CpSolverStatus::OPTIMAL) {
542 std::vector<int64_t>
solution(result.solution().begin(),
543 result.solution().end());
544 for (
int i = solution_postprocessors_.size(); --
i >= 0;) {
551 for (
int i = postprocessors_.size(); --
i >= 0;) {
552 postprocessors_[
i](&result);
558 absl::MutexLock mutex_lock(&mutex_);
559 CpSolverResponse result =
561 ? GetResponseInternal({},
"")
562 : GetResponseInternal(solutions_.
GetSolution(0).variable_values,
566 if (parameters_.fill_additional_solutions_in_response()) {
567 std::vector<int64_t> temp;
570 for (
int i = solution_postprocessors_.size(); --
i >= 0;) {
571 solution_postprocessors_[
i](&temp);
573 result.add_additional_solutions()->mutable_values()->Assign(temp.begin(),
580 for (
int i = final_postprocessors_.size(); --
i >= 0;) {
581 final_postprocessors_[
i](&result);
588 const CpSolverResponse& response) {
589 absl::MutexLock mutex_lock(&mutex_);
590 return subsolver_responses_.push_back(response);
593void SharedResponseManager::FillObjectiveValuesInResponse(
594 CpSolverResponse* response)
const {
595 if (objective_or_null_ ==
nullptr)
return;
596 const CpObjectiveProto& obj = *objective_or_null_;
598 if (best_status_ == CpSolverStatus::INFEASIBLE) {
599 response->clear_objective_value();
600 response->clear_best_objective_bound();
601 response->clear_inner_objective_lower_bound();
607 if (best_status_ == CpSolverStatus::UNKNOWN) {
608 response->set_objective_value(
611 response->set_objective_value(
616 response->set_inner_objective_lower_bound(
618 response->set_best_objective_bound(
622 response->set_gap_integral(gap_integral_);
626 absl::Span<const int64_t> solution_values,
const std::string& solution_info,
628 absl::MutexLock mutex_lock(&mutex_);
632 if (objective_or_null_ ==
nullptr) {
635 solution_values.end());
645 solution_values.end());
668 if (always_synchronize_) {
670 first_solution_solvers_should_stop_ =
true;
675 if (objective_or_null_ ==
nullptr && !parameters_.enumerate_all_solutions()) {
676 UpdateBestStatus(CpSolverStatus::OPTIMAL);
678 UpdateBestStatus(CpSolverStatus::FEASIBLE);
682 if (objective_or_null_ !=
nullptr &&
683 inner_objective_lower_bound_ > inner_objective_upper_bound_) {
684 UpdateBestStatus(CpSolverStatus::OPTIMAL);
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);
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,
"/",
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));
717 if (objective_or_null_ !=
nullptr) {
718 const CpObjectiveProto& obj = *objective_or_null_;
723 if (obj.scaling_factor() < 0) {
726 RegisterSolutionFound(solution_message, num_solutions_);
727 SOLVER_LOG(logger_, ProgressMessage(absl::StrCat(num_solutions_),
728 wall_timer_.
Get(), best, lb, ub,
732 SatProgressMessage(absl::StrCat(num_solutions_),
733 wall_timer_.
Get(), solution_message));
739 TestGapLimitsIfNeeded();
740 for (
const auto& pair : callbacks_) {
741 pair.second(tmp_postsolved_response);
744#if !defined(__PORTABLE_PLATFORM__)
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 <<
"'.";
755 CpSolverResponse response;
756 response.mutable_solution()->Assign(solution_values.begin(),
757 solution_values.end());
764 absl::MutexLock mutex_lock(&mutex_);
765 return synchronized_best_status_ == CpSolverStatus::OPTIMAL ||
766 synchronized_best_status_ == CpSolverStatus::INFEASIBLE;
769void SharedResponseManager::UpdateBestStatus(
const CpSolverStatus&
status) {
771 if (always_synchronize_) {
772 synchronized_best_status_ =
status;
777 if (improvement_info.empty())
return "";
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);
786 return improvement_info;
789void SharedResponseManager::RegisterSolutionFound(
790 const std::string& improvement_info,
int solution_rank) {
791 if (improvement_info.empty())
return;
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;
798void SharedResponseManager::RegisterObjectiveBoundImprovement(
799 const std::string& improvement_info) {
800 if (improvement_info.empty() || improvement_info ==
"initial domain")
return;
805 absl::MutexLock mutex_lock(&mutex_);
806 if (!primal_improvements_count_.empty()) {
807 std::vector<std::vector<std::string>> table;
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];
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_) {
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];
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;
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;
867 VLOG(3) << worker_name <<
" var=" <<
var <<
" [" << old_lb <<
"," << old_ub
868 <<
"] -> [" << new_lb <<
"," << new_ub <<
"]";
872 CHECK_LE(new_lb, debug_solution_[
var]) << worker_name <<
" var=" <<
var;
874 lower_bounds_[
var] = new_lb;
878 CHECK_GE(new_ub, debug_solution_[
var]) << worker_name <<
" var=" <<
var;
880 upper_bounds_[
var] = new_ub;
882 changed_variables_since_last_synchronize_.Set(
var);
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);
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 <<
"'.";
911 const std::vector<int64_t>&
solution,
912 const std::vector<int>& variables_to_fix) {
913 absl::MutexLock mutex_lock(&mutex_);
919 for (
const int var : variables_to_fix) {
922 VLOG(1) <<
"Incompatibility in FixVariablesFromPartialSolution() "
923 <<
"var: " <<
var <<
" value: " <<
value <<
" bounds: ["
924 << lower_bounds_[
var] <<
"," << upper_bounds_[
var] <<
"]";
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];
935 if (!changed_lb && !changed_ub)
continue;
939 changed_variables_since_last_synchronize_.Set(
var);
946 LOG(INFO) <<
"Fixing to a different solution for var=" <<
var
947 <<
" debug=" << debug_solution_[
var]
949 lower_bounds_[
var] = debug_solution_[
var];
950 upper_bounds_[
var] = debug_solution_[
var];
957 absl::MutexLock mutex_lock(&mutex_);
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);
966 changed_variables_since_last_synchronize_.ClearAll();
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);
987 int id, std::vector<int>* variables, std::vector<int64_t>* new_lower_bounds,
988 std::vector<int64_t>* new_upper_bounds) {
990 new_lower_bounds->clear();
991 new_upper_bounds->clear();
993 absl::MutexLock mutex_lock(&mutex_);
994 for (
const int var : id_to_changed_variables_[
id].PositionsSetAtLeastOnce()) {
995 variables->push_back(
var);
997 id_to_changed_variables_[id].ClearAll();
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]);
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]));
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_) {
1031 absl::MutexLock mutex_lock(&mutex_);
1032 const auto it = bounds_exported_.find(worker_name);
1033 if (it == bounds_exported_.end())
return 0;
1038 for (
auto& buffer : clauses_by_size_) {
1044 absl::MutexLock mutex_lock(&mutex_);
1045 if (clause.size() >
kMaxClauseSize || clause.size() <= 2)
return false;
1050 if (BlockClause(clause)) {
1051 std::vector<int>* buffer = MutableBufferForSize(clause.size());
1052 buffer->insert(buffer->end(), clause.begin(), clause.end());
1058bool UniqueClauseStream::BlockClause(absl::Span<const int> clause) {
1060 if (clause.size() <= 2)
return false;
1061 return fingerprints_.emplace(
HashClause(clause)).second;
1065 const size_t fingerprint =
HashClause(clause);
1066 absl::MutexLock mutex_lock(&mutex_);
1068 return fingerprints_.erase(fingerprint) == 1;
1075 absl::MutexLock mutex_lock(&mutex_);
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))) {
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);
1099 if (fingerprints_.contains(
HashClause(clause)) && upstream.
Add(clause)) {
1100 ++num_exported_clauses;
1104 return num_exported_clauses;
1108 absl::MutexLock mutex_lock(&mutex_);
1110 for (
const auto& buffer : clauses_by_size_) {
1111 result += buffer.size();
1118 absl::MutexLock mutex_lock(&mutex_);
1119 if (lbd > lbd_threshold_)
return false;
1120 int num_literals_up_to_size = 0;
1122 num_literals_up_to_size += NumLiteralsOfSize(
i);
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();
1135 while (NumLiteralsOfSize(
size) > 0) {
1139 if (literals_to_remove <
size)
return;
1142 literals_to_remove -=
size;
1148 absl::MutexLock mutex_lock(&mutex_);
1149 lbd_threshold_ = lbd;
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);
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);
1166void UniqueClauseStream::PopClause(
int size) {
1167 std::vector<int>* buffer = MutableBufferForSize(
size);
1168 buffer->erase(buffer->end() -
size, buffer->end());
1171int UniqueClauseStream::NumClausesOfSize(
int size)
const {
1172 return NumLiteralsOfSize(
size) /
size;
1175int UniqueClauseStream::NumLiteralsOfSize(
int size)
const {
1176 return BufferForSize(
size).size();
1180 absl::Duration share_frequency)
1181 : always_synchronize_(always_synchronize),
1182 share_frequency_(share_frequency) {}
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();
1196 const std::string& worker_name) {
1197 absl::MutexLock mutex_lock(&mutex_);
1198 id_to_worker_name_[id] = worker_name;
1202 if (lit2 < lit1) std::swap(lit1, lit2);
1203 const auto p = std::make_pair(lit1, lit2);
1205 absl::MutexLock mutex_lock(&mutex_);
1206 const auto [unused_it, inserted] = added_binary_clauses_set_.insert(p);
1208 added_binary_clauses_.push_back(p);
1209 if (always_synchronize_) ++last_visible_binary_clause_;
1210 id_to_clauses_exported_[id]++;
1214 if (id_to_last_processed_binary_clause_[
id] ==
1215 added_binary_clauses_.size() - 1) {
1216 id_to_last_processed_binary_clause_[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]);
1230 id_to_last_finished_batch_[id] = id_to_last_returned_batch_[id];
1231 id_to_last_returned_batch_[id] = batches_.size();
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;
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_;
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];
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) {
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();
1276 for (
int id = 0;
id < num_workers; ++id) {
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;
1295 std::vector<int> ids(num_workers);
1300 for (
int id = 0;
id < num_workers; ++id) {
1301 if (id_to_clause_stream_[
id].NumBufferedLiteralsOfSize(
size) > 0) {
1307 while (!ids.empty()) {
1308 const int clauses_to_fill = literals_to_fill /
size;
1309 if (clauses_to_fill == 0)
break;
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;
1320 id_to_clause_stream_[
id].NumBufferedLiteralsOfSize(
size) == 0) {
1321 ids[
i] = ids.back();
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();
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]);
1349 batches_.pop_front();
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;
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;
1367 absl::MutexLock mutex_lock(&mutex_);
1368 if (stats_.empty())
return;
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});
1375 std::sort(to_sort_.begin(), to_sort_.end());
1376 for (
const auto& [key, count] : to_sort_) {
Domain IntersectionWith(const Domain &domain) const
double GetElapsedDeterministicTime() const
void reserve(int size)
Reserve memory if it is already known or tightly estimated.
int Add(absl::Span< const V > values)
The model "singleton" shared time limit.
void UpdateDomains(std::vector< Domain > *domains)
void LogStatistics(SolverLogger *logger)
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)
IntegerValue GetInnerObjectiveLowerBound()
void LogMessageWithThrottling(const std::string &prefix, const std::string &message)
void SetUpdateGapIntegralOnEachChange(bool set)
bool ProblemIsSolved() const
SharedResponseManager(Model *model)
double GapIntegral() const
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 UnregisterLogCallback(int callback_id)
void UnregisterCallback(int callback_id)
IntegerValue BestSolutionInnerObjectiveValue()
void UnregisterBestBoundCallback(int callback_id)
void DisplayImprovementStatistics()
Display improvement stats.
bool LoggingIsEnabled() const
void InitializeObjective(const CpModelProto &cp_model)
void SetGapLimitsFromParameters(const SatParameters ¶meters)
void AddFinalResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
void AddResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
CpSolverResponse GetResponse()
int AddSolutionCallback(std::function< void(const CpSolverResponse &)> callback)
IntegerValue GetInnerObjectiveUpperBound()
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 Add(const Solution &solution)
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.
static constexpr int kMaxClauseSize
static constexpr int kMaxBufferedLiterals
int FillUpstreamBuffer(UniqueClauseStream &upstream, int clause_size, int max_clauses_to_export) ABSL_LOCKS_EXCLUDED(mutex_)
int NumBufferedLiterals() const ABSL_LOCKS_EXCLUDED(mutex_)
bool CanAccept(int size, int lbd) const
void RemoveWorstClauses()
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 kMinClauseSize
static constexpr int kMaxLiteralsPerBatch
Export 4KiB of clauses per batch.
absl::Status SetTextProto(absl::string_view filename, const google::protobuf::Message &proto, Options options)
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).
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.
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
In SWIG mode, we don't want anything besides these top-level includes.
std::vector< std::function< void(CpSolverResponse *)> > callbacks
The solution format used by this class.
std::vector< ValueType > variable_values
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\"'.")
double objective_value
The value objective_vector^T * (solution - center_point).
#define SOLVER_LOG(logger,...)