34#include "absl/algorithm/container.h"
38#if !defined(__PORTABLE_PLATFORM__)
42#include "absl/base/thread_annotations.h"
43#include "absl/container/btree_map.h"
44#include "absl/container/flat_hash_map.h"
45#include "absl/container/flat_hash_set.h"
46#include "absl/flags/flag.h"
47#include "absl/hash/hash.h"
48#include "absl/log/check.h"
49#include "absl/log/log.h"
50#include "absl/numeric/int128.h"
51#include "absl/random/bit_gen_ref.h"
52#include "absl/random/distributions.h"
53#include "absl/strings/str_cat.h"
54#include "absl/strings/str_format.h"
55#include "absl/strings/string_view.h"
56#include "absl/synchronization/mutex.h"
57#include "absl/types/span.h"
72 "DEBUG ONLY. If true, all the intermediate solution will be dumped "
73 "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.");
74ABSL_FLAG(
bool, cp_model_dump_tightened_models,
false,
75 "DEBUG ONLY. If true, dump tightened models incoporating all bounds "
76 "changes under '\"FLAGS_cp_model_dump_prefix\" + "
77 "\"tight_model_xxx.pb.txt\"'.");
82std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
85 if (alternative_path_.num_solutions_to_keep() > 0 &&
86 solution.source_id == alternative_path_.source_id()) {
88 if (
solution.rank < best_solutions_.GetBestRank()) {
89 VLOG(2) <<
"ALTERNATIVE WIN !";
94 return best_solutions_.Add(std::move(
solution));
99 if (alternative_path_.num_solutions_to_keep() > 0) {
100 absl::MutexLock mutex_lock(mutex_);
102 auto process_solution =
104 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
105 if (
solution.variable_values.empty())
return;
108 min_rank_ = std::min(min_rank_,
solution.rank);
109 max_rank_ = std::max(max_rank_,
solution.rank);
112 int num_solutions = std::max<int>(
113 10, 100'000'000 /
solution.variable_values.size());
114 const int64_t range = max_rank_ - min_rank_ + 1;
115 if (num_solutions > range) {
116 num_solutions = range;
125 num_solutions = std::min(num_solutions, 1'000);
132 ranks_.resize(num_solutions);
133 seeds_.resize(num_solutions);
135 int64_t offset = (max_rank_ - min_rank_ + 1) / num_solutions;
137 for (
int i = 0;
i < num_solutions; ++
i) {
138 ranks_[
i] = min_rank_ +
139 static_cast<int64_t
>(absl::int128(
i) *
140 absl::int128(range) /
141 absl::int128(num_solutions));
145 int to_index = seeds_.size() - 1;
146 for (
int i = seeds_.size(); --
i >= 0;) {
147 if (seeds_[
i] ==
nullptr)
continue;
148 while (to_index >= 0 && ranks_[to_index] > seeds_[
i]->rank) {
151 seeds_[to_index] = std::move(seeds_[
i]);
156 const int limit = std::upper_bound(ranks_.begin(), ranks_.end(),
161 std::make_shared<SharedSolutionRepository<int64_t>::Solution>(
167 best_solutions_.Synchronize(process_solution);
169 best_solutions_.Synchronize();
171 alternative_path_.Synchronize();
178 if (alternative_path_.num_solutions_to_keep() > 0) {
180 const int threshold = std::max(
181 100,
static_cast<int>(std::sqrt(best_solutions_.num_queried())));
182 if (alternative_path_.NumRecentlyNonImproving() > threshold) {
183 VLOG(2) <<
"Done. num_non_improving: "
184 << alternative_path_.NumRecentlyNonImproving()
185 <<
" achieved: " << alternative_path_.GetBestRank() <<
" / "
186 << best_solutions_.GetBestRank();
187 alternative_path_.ClearSolutionsAndIncreaseSourceId();
191 if (alternative_path_.NumSolutions() == 0) {
192 absl::MutexLock mutex_lock(mutex_);
196 if (seeds_.size() > 1) {
199 int index = 1 + absl::LogUniform<int>(random, 0, seeds_.size() - 2);
200 for (; index < seeds_.size(); ++index) {
201 if (seeds_[index] !=
nullptr) {
202 alternative_path_.Add(*seeds_[index]);
203 alternative_path_.Synchronize();
204 VLOG(2) <<
"RESTART bucket=" << index <<
"/" << seeds_.size()
205 <<
" rank=" << alternative_path_.GetSolution(0)->rank
207 << alternative_path_.GetSolution(0)->rank - min_rank_;
213 CHECK(seeds_.back() !=
nullptr);
214 CHECK_LT(index, seeds_.size());
221 std::vector<double> lp_solution) {
222 if (lp_solution.empty())
return;
226 std::make_shared<SharedSolutionRepository<double>::Solution>();
227 solution->variable_values = std::move(lp_solution);
231 absl::MutexLock mutex_lock(
mutex_);
232 solution->rank = -num_synchronization_;
239 const std::vector<double>& lp_solution) {
240 absl::MutexLock mutex_lock(mutex_);
242 solutions_.push_back(lp_solution);
243 if (solutions_.size() > 100) solutions_.pop_front();
247 absl::MutexLock mutex_lock(mutex_);
248 return !solutions_.empty();
252 absl::MutexLock mutex_lock(mutex_);
253 if (solutions_.empty())
return {};
256 std::vector<double>
solution = std::move(solutions_.back());
257 solutions_.pop_back();
263 wall_timer_(*model->GetOrCreate<
WallTimer>()),
266 solution_pool_(parameters_),
268 bounds_logging_id_ = logger_->GetNewThrottledId();
273 if (model ==
nullptr)
return;
274 absl::MutexLock mutex_lock(mutex_);
275 for (
const auto& set_stats : statistics_postprocessors_) {
276 set_stats(model, response);
281 absl::string_view message) {
282 absl::MutexLock mutex_lock(mutex_);
283 SOLVER_LOG(logger_, absl::StrFormat(
"#%-5s %6.2fs %s", prefix,
284 wall_timer_.Get(), message));
288 absl::string_view prefix, absl::string_view message) {
289 absl::MutexLock mutex_lock(mutex_);
292 auto it = throttling_ids_.find(prefix);
293 if (it == throttling_ids_.end()) {
294 id = throttling_ids_[prefix] = logger_->GetNewThrottledId();
298 logger_->ThrottledLog(
id, absl::StrFormat(
"#%-5s %6.2fs %s", prefix,
299 wall_timer_.Get(), message));
303 absl::MutexLock mutex_lock(mutex_);
305 return logger_->LoggingIsEnabled();
310 objective_or_null_ = &cp_model.
objective();
314 IntegerValue(domain.
Max()));
317 objective_or_null_ =
nullptr;
322 absl::MutexLock mutex_lock(mutex_);
323 always_synchronize_ = always_synchronize;
327 absl::MutexLock mutex_lock(mutex_);
328 update_integral_on_each_change_ = set;
332 absl::MutexLock mutex_lock(mutex_);
333 UpdateGapIntegralInternal();
336void SharedResponseManager::UpdateGapIntegralInternal() {
337 if (objective_or_null_ ==
nullptr)
return;
340 const double time_delta = current_time - last_gap_integral_time_stamp_;
348 const double factor =
350 const double bounds_delta = std::log(1 + factor * last_absolute_gap_);
351 gap_integral_ += time_delta * bounds_delta;
354 last_gap_integral_time_stamp_ = current_time;
356 std::max(0.0,
static_cast<double>(inner_objective_upper_bound_) -
357 static_cast<double>(inner_objective_lower_bound_));
362 absl::MutexLock mutex_lock(mutex_);
363 if (objective_or_null_ ==
nullptr)
return;
368void SharedResponseManager::TestGapLimitsIfNeeded() {
372 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
376 if (absolute_gap_limit_ == 0 && relative_gap_limit_ == 0)
return;
379 if (inner_objective_lower_bound_ > inner_objective_upper_bound_)
return;
382 const double user_best =
384 const double user_bound =
386 const double gap = std::abs(user_best - user_bound);
387 if (gap <= absolute_gap_limit_) {
388 SOLVER_LOG(logger_,
"Absolute gap limit of ", absolute_gap_limit_,
395 if (always_synchronize_) shared_time_limit_->
Stop();
397 if (gap / std::max(1.0, std::abs(user_best)) < relative_gap_limit_) {
398 SOLVER_LOG(logger_,
"Relative gap limit of ", relative_gap_limit_,
403 if (always_synchronize_) shared_time_limit_->
Stop();
408 const std::string& update_info, IntegerValue lb, IntegerValue ub) {
409 absl::MutexLock mutex_lock(mutex_);
410 CHECK(objective_or_null_ !=
nullptr);
417 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
421 const bool ub_change = ub < inner_objective_upper_bound_;
422 const bool lb_change = lb > inner_objective_lower_bound_;
423 if (!lb_change && !ub_change)
return;
430 DCHECK_LE(inner_objective_upper_bound_, best_solution_objective_value_);
431 inner_objective_lower_bound_ =
432 std::min(best_solution_objective_value_, lb.value());
435 inner_objective_upper_bound_ = ub.value();
438 if (always_synchronize_) {
439 synchronized_inner_objective_lower_bound_ =
440 IntegerValue(inner_objective_lower_bound_);
441 synchronized_inner_objective_upper_bound_ =
442 IntegerValue(inner_objective_upper_bound_);
445 if (!status_change_callbacks_.empty()) {
448 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
454 for (
const auto& callback : status_change_callbacks_) {
459 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
466 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
469 if (!best_bound_callbacks_.empty()) {
473 for (
const auto& callback_entry : best_bound_callbacks_) {
474 callback_entry.second(new_lb);
478 TestGapLimitsIfNeeded();
485 absl::string_view worker_info) {
486 absl::MutexLock mutex_lock(mutex_);
495 inner_objective_lower_bound_ = best_solution_objective_value_;
496 if (update_integral_on_each_change_) UpdateGapIntegralInternal();
498 CHECK_EQ(num_solutions_, 0);
501 if (!status_change_callbacks_.empty()) {
505 for (
const auto& callback : status_change_callbacks_) {
512 absl::MutexLock mutex_lock(mutex_);
517 absl::MutexLock mutex_lock(mutex_);
518 return synchronized_inner_objective_lower_bound_;
522 absl::MutexLock mutex_lock(mutex_);
523 return synchronized_inner_objective_upper_bound_;
527 solution_pool_.Synchronize(*random_);
529 absl::MutexLock mutex_lock(mutex_);
530 synchronized_inner_objective_lower_bound_ =
531 IntegerValue(inner_objective_lower_bound_);
532 synchronized_inner_objective_upper_bound_ =
533 IntegerValue(inner_objective_upper_bound_);
534 synchronized_best_status_ = best_status_;
535 if (solution_pool_.BestSolutions().NumSolutions() > 0) {
536 first_solution_solvers_should_stop_ =
true;
538 logger_->FlushPendingThrottledLogs();
542 absl::MutexLock mutex_lock(mutex_);
543 return IntegerValue(best_solution_objective_value_);
547 absl::MutexLock mutex_lock(mutex_);
548 return gap_integral_;
552 std::function<
void(std::vector<int64_t>*)> postprocessor) {
553 absl::MutexLock mutex_lock(mutex_);
554 solution_postprocessors_.push_back(postprocessor);
559 absl::MutexLock mutex_lock(mutex_);
560 postprocessors_.push_back(postprocessor);
565 absl::MutexLock mutex_lock(mutex_);
566 final_postprocessors_.push_back(postprocessor);
571 absl::MutexLock mutex_lock(mutex_);
572 statistics_postprocessors_.push_back(postprocessor);
577 absl::MutexLock mutex_lock(mutex_);
578 const int id = next_callback_id_++;
579 callbacks_.emplace_back(
id, std::move(callback));
584 absl::MutexLock mutex_lock(mutex_);
585 for (
int i = 0;
i < callbacks_.size(); ++
i) {
586 if (callbacks_[
i].first == callback_id) {
587 callbacks_.erase(callbacks_.begin() +
i);
591 LOG(DFATAL) <<
"Callback id " << callback_id <<
" not registered.";
596 absl::MutexLock mutex_lock(mutex_);
597 const int id = next_search_log_callback_id_++;
598 search_log_callbacks_.emplace_back(
id, std::move(callback));
604 absl::MutexLock mutex_lock(mutex_);
605 status_change_callbacks_.push_back(std::move(callback));
609 absl::MutexLock mutex_lock(mutex_);
610 for (
int i = 0;
i < search_log_callbacks_.size(); ++
i) {
611 if (search_log_callbacks_[
i].first == callback_id) {
612 search_log_callbacks_.erase(search_log_callbacks_.begin() +
i);
616 LOG(DFATAL) <<
"Callback id " << callback_id <<
" not registered.";
620 std::function<
void(
double)> callback) {
621 absl::MutexLock mutex_lock(mutex_);
622 const int id = next_best_bound_callback_id_++;
623 best_bound_callbacks_.emplace_back(
id, std::move(callback));
628 absl::MutexLock mutex_lock(mutex_);
629 for (
int i = 0;
i < best_bound_callbacks_.size(); ++
i) {
630 if (best_bound_callbacks_[
i].first == callback_id) {
631 best_bound_callbacks_.erase(best_bound_callbacks_.begin() +
i);
635 LOG(DFATAL) <<
"Callback id " << callback_id <<
" not registered.";
639 absl::Span<const int64_t> variable_values,
640 absl::string_view solution_info) {
643 if (!unsat_cores_.empty()) {
646 unsat_cores_.begin(), unsat_cores_.end());
648 FillObjectiveValuesInResponse(&result);
659 variable_values.end());
664 if (!subsolver_responses_.empty()) {
665 result.
MergeFrom(subsolver_responses_.front());
673 for (
int i = solution_postprocessors_.size(); --
i >= 0;) {
680 for (
int i = postprocessors_.size(); --
i >= 0;) {
681 postprocessors_[
i](&result);
687 absl::MutexLock mutex_lock(mutex_);
689 if (solution_pool_.BestSolutions().NumSolutions() == 0) {
690 result = GetResponseInternal({},
"");
692 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
693 solution = solution_pool_.BestSolutions().GetSolution(0);
697 if (parameters_.fill_additional_solutions_in_response()) {
698 std::vector<int64_t> temp;
699 const int size = solution_pool_.BestSolutions().NumSolutions();
700 for (
int i = 0;
i < size; ++
i) {
701 const auto solution = solution_pool_.BestSolutions().GetSolution(
i);
703 for (
int i = solution_postprocessors_.size(); --
i >= 0;) {
704 solution_postprocessors_[
i](&temp);
713 for (
int i = final_postprocessors_.size(); --
i >= 0;) {
714 final_postprocessors_[
i](&result);
722 absl::MutexLock mutex_lock(mutex_);
723 return subsolver_responses_.push_back(response);
726void SharedResponseManager::FillObjectiveValuesInResponse(
728 if (objective_or_null_ ==
nullptr)
return;
758std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
760 absl::string_view solution_info,
761 Model* model,
int source_id) {
762 absl::MutexLock mutex_lock(mutex_);
763 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution> ret;
767 if (objective_or_null_ ==
nullptr) {
769 solution.variable_values.assign(solution_values.begin(),
770 solution_values.end());
775 const int64_t objective_value =
780 solution.variable_values.assign(solution_values.begin(),
781 solution_values.end());
788 if (objective_value > inner_objective_upper_bound_)
return ret;
794 DCHECK_GE(objective_value, inner_objective_lower_bound_);
796 DCHECK_LT(objective_value, best_solution_objective_value_);
797 best_solution_objective_value_ = objective_value;
800 inner_objective_upper_bound_ = objective_value - 1;
805 if (always_synchronize_) {
806 solution_pool_.Synchronize(*random_);
807 first_solution_solvers_should_stop_ =
true;
812 if (objective_or_null_ ==
nullptr && !parameters_.enumerate_all_solutions()) {
819 if (objective_or_null_ !=
nullptr &&
820 inner_objective_lower_bound_ > inner_objective_upper_bound_) {
829 if ((!search_log_callbacks_.empty() && logger_->LoggingIsEnabled()) ||
830 !callbacks_.empty()) {
831 tmp_postsolved_response =
832 GetResponseInternal(solution_values, solution_info);
835 if (model !=
nullptr && !statistics_postprocessors_.empty()) {
836 for (
const auto& set_stats : statistics_postprocessors_) {
837 set_stats(model, &tmp_postsolved_response);
842 std::string solution_message(solution_info);
844 absl::StrAppend(&solution_message,
" (fixed_bools=",
849 if (logger_->LoggingIsEnabled() && !search_log_callbacks_.empty()) {
850 for (
const auto& pair : search_log_callbacks_) {
851 absl::StrAppend(&solution_message,
" ",
852 pair.second(tmp_postsolved_response));
856 if (!status_change_callbacks_.empty()) {
860 for (
const auto& callback : status_change_callbacks_) {
867 TestGapLimitsIfNeeded();
868 for (
const auto& pair : callbacks_) {
869 pair.second(tmp_postsolved_response);
872#if !defined(__PORTABLE_PLATFORM__)
875 if (logger_->LoggingIsEnabled() &&
876 absl::GetFlag(FLAGS_cp_model_dump_solutions)) {
877 const std::string
file =
878 absl::StrCat(dump_prefix_,
"solution_", num_solutions_,
".pb.txt");
879 LOG(INFO) <<
"Dumping solution to '" <<
file <<
"'.";
885 solution_values.end());
894 absl::MutexLock mutex_lock(mutex_);
899void SharedResponseManager::UpdateBestStatus(
const CpSolverStatus& status) {
900 best_status_ = status;
901 if (always_synchronize_) {
902 synchronized_best_status_ = status;
906SolverStatusChangeInfo SharedResponseManager::GetSolverStatusChangeInfo() {
907 SolverStatusChangeInfo result;
908 if (objective_or_null_ ==
nullptr)
return result;
909 const CpObjectiveProto& obj = *objective_or_null_;
913 if (obj.scaling_factor() < 0) {
916 result.best_objective_value = best;
917 result.cur_objective_value_lb = lb;
918 result.cur_objective_value_ub = ub;
923 : num_variables_(model_proto.variables_size()),
924 model_proto_(model_proto),
925 lower_bounds_(num_variables_,
std::numeric_limits<int64_t>::min()),
926 upper_bounds_(num_variables_,
std::numeric_limits<int64_t>::max()),
927 synchronized_lower_bounds_(num_variables_,
928 std::numeric_limits<int64_t>::min()),
929 synchronized_upper_bounds_(num_variables_,
930 std::numeric_limits<int64_t>::max()) {
931 changed_variables_since_last_synchronize_.ClearAndResize(num_variables_);
932 for (
int i = 0;
i < num_variables_; ++
i) {
936 synchronized_lower_bounds_[
i] = lower_bounds_[
i];
937 synchronized_upper_bounds_[
i] = upper_bounds_[
i];
942 const int num_vars = model_proto.variables().size();
943 std::vector<std::unique_ptr<SparsePermutation>> generators;
944 for (const SparsePermutationProto& perm :
945 model_proto.symmetry().permutations()) {
946 generators.emplace_back(CreateSparsePermutationFromProto(num_vars, perm));
948 if (generators.empty())
return;
951 var_to_orbit_index_ =
GetOrbits(num_vars, generators);
954 std::vector<int> keys;
955 std::vector<int> values;
956 for (
int var = 0; var < num_vars; ++var) {
957 const int orbit_index = var_to_orbit_index_[var];
958 if (orbit_index == -1)
continue;
959 keys.push_back(orbit_index);
960 values.push_back(var);
962 if (keys.empty())
return;
964 has_symmetry_ =
true;
965 orbits_.ResetFromFlatMapping(keys, values);
968 var_to_representative_.resize(num_vars);
969 for (
int var = 0; var < num_vars; ++var) {
970 const int orbit_index = var_to_orbit_index_[var];
971 if (orbit_index == -1) {
972 var_to_representative_[var] = var;
974 var_to_representative_[var] = orbits_[orbit_index][0];
981 const std::string& worker_name, absl::Span<const int> variables,
982 absl::Span<const int64_t> new_lower_bounds,
983 absl::Span<const int64_t> new_upper_bounds) {
984 CHECK_EQ(variables.size(), new_lower_bounds.size());
985 CHECK_EQ(variables.size(), new_upper_bounds.size());
986 int num_improvements = 0;
987 int num_symmetric_improvements = 0;
989 absl::MutexLock mutex_lock(mutex_);
990 for (
int i = 0;
i < variables.size(); ++
i) {
991 int var = variables[
i];
992 if (var >= num_variables_)
continue;
996 var = var_to_representative_[var];
998 const int64_t old_lb = lower_bounds_[var];
999 const int64_t old_ub = upper_bounds_[var];
1000 const int64_t new_lb = new_lower_bounds[
i];
1001 const int64_t new_ub = new_upper_bounds[
i];
1002 const bool changed_lb = new_lb > old_lb;
1003 const bool changed_ub = new_ub < old_ub;
1004 if (!changed_lb && !changed_ub)
continue;
1006 VLOG(3) << worker_name <<
" var=" << var <<
" [" << old_lb <<
"," << old_ub
1007 <<
"] -> [" << new_lb <<
"," << new_ub <<
"]";
1010 if (
DEBUG_MODE && !debug_solution_.empty()) {
1011 CHECK_LE(new_lb, debug_solution_[var]) << worker_name <<
" var=" << var;
1013 lower_bounds_[var] = new_lb;
1016 if (
DEBUG_MODE && !debug_solution_.empty()) {
1017 CHECK_GE(new_ub, debug_solution_[var]) << worker_name <<
" var=" << var;
1019 upper_bounds_[var] = new_ub;
1021 changed_variables_since_last_synchronize_.Set(var);
1024 if (has_symmetry_ && variables[
i] != var) {
1027 num_symmetric_improvements +=
1028 orbits_[var_to_orbit_index_[var]].size() - 1;
1031 if (num_improvements > 0) {
1032 total_num_improvements_ += num_improvements;
1033 VLOG(3) << total_num_improvements_ <<
"/" << num_variables_;
1034 bounds_exported_[worker_name].num_exported += num_improvements;
1035 bounds_exported_[worker_name].num_symmetric += num_symmetric_improvements;
1036 if (absl::GetFlag(FLAGS_cp_model_dump_tightened_models)) {
1038 for (
int i = 0;
i < num_variables_; ++
i) {
1042 if (has_symmetry_) rep = var_to_representative_[
i];
1044 .IntersectionWith(
Domain(lower_bounds_[rep],
1045 upper_bounds_[rep]));
1048 const std::string filename = absl::StrCat(dump_prefix_,
"tighened_model_",
1049 export_counter_,
".pb.txt");
1050 LOG(INFO) <<
"Dumping tightened model proto to '" << filename <<
"'.";
1061 absl::Span<const int64_t>
solution,
1062 absl::Span<const int> variables_to_fix) {
1064 CHECK(!has_symmetry_);
1065 absl::MutexLock mutex_lock(mutex_);
1071 for (
const int var : variables_to_fix) {
1072 const int64_t value =
solution[var];
1073 if (value < lower_bounds_[var] || value > upper_bounds_[var]) {
1074 VLOG(1) <<
"Incompatibility in FixVariablesFromPartialSolution() "
1075 <<
"var: " << var <<
" value: " << value <<
" bounds: ["
1076 << lower_bounds_[var] <<
"," << upper_bounds_[var] <<
"]";
1082 for (
const int var : variables_to_fix) {
1083 const int64_t old_lb = lower_bounds_[var];
1084 const int64_t old_ub = upper_bounds_[var];
1085 const bool changed_lb =
solution[var] > old_lb;
1086 const bool changed_ub =
solution[var] < old_ub;
1087 if (!changed_lb && !changed_ub)
continue;
1089 lower_bounds_[var] =
solution[var];
1090 upper_bounds_[var] =
solution[var];
1091 changed_variables_since_last_synchronize_.Set(var);
1096 if (
DEBUG_MODE && !debug_solution_.empty()) {
1097 if (
solution[var] != debug_solution_[var]) {
1098 LOG(INFO) <<
"Fixing to a different solution for var=" << var
1099 <<
" debug=" << debug_solution_[var]
1101 lower_bounds_[var] = debug_solution_[var];
1102 upper_bounds_[var] = debug_solution_[var];
1109 absl::MutexLock mutex_lock(mutex_);
1110 for (
const int var :
1111 changed_variables_since_last_synchronize_.PositionsSetAtLeastOnce()) {
1112 DCHECK(!has_symmetry_ || var_to_representative_[var] == var);
1113 synchronized_lower_bounds_[var] = lower_bounds_[var];
1114 synchronized_upper_bounds_[var] = upper_bounds_[var];
1115 for (
int j = 0; j < id_to_changed_variables_.size(); ++j) {
1116 id_to_changed_variables_[j].Set(var);
1119 changed_variables_since_last_synchronize_.ResetAllToFalse();
1123 absl::MutexLock mutex_lock(mutex_);
1124 const int id = id_to_changed_variables_.size();
1125 id_to_changed_variables_.resize(
id + 1);
1126 id_to_changed_variables_[id].ClearAndResize(num_variables_);
1127 for (
int var = 0; var < num_variables_; ++var) {
1128 const int64_t lb = model_proto_.variables(var).domain(0);
1129 const int domain_size = model_proto_.variables(var).domain_size();
1130 const int64_t ub = model_proto_.variables(var).domain(domain_size - 1);
1131 if (lb != synchronized_lower_bounds_[var] ||
1132 ub != synchronized_upper_bounds_[var]) {
1133 DCHECK(!has_symmetry_ || var_to_representative_[var] == var);
1134 id_to_changed_variables_[id].Set(var);
1141 int id, std::vector<int>* variables, std::vector<int64_t>* new_lower_bounds,
1142 std::vector<int64_t>* new_upper_bounds) {
1144 new_lower_bounds->clear();
1145 new_upper_bounds->clear();
1148 absl::MutexLock mutex_lock(mutex_);
1149 for (
const int var :
1150 id_to_changed_variables_[
id].PositionsSetAtLeastOnce()) {
1151 DCHECK(!has_symmetry_ || var_to_representative_[var] == var);
1152 variables->push_back(var);
1154 id_to_changed_variables_[id].ResetAllToFalse();
1159 absl::c_sort(*variables);
1160 for (
const int var : *variables) {
1161 new_lower_bounds->push_back(synchronized_lower_bounds_[var]);
1162 new_upper_bounds->push_back(synchronized_upper_bounds_[var]);
1170 if (has_symmetry_) {
1171 const int old_size = variables->size();
1172 for (
int i = 0;
i < old_size; ++
i) {
1173 const int var = (*variables)[
i];
1174 const int orbit_index = var_to_orbit_index_[var];
1175 if (orbit_index == -1)
continue;
1177 const int64_t lb = (*new_lower_bounds)[
i];
1178 const int64_t ub = (*new_upper_bounds)[
i];
1179 const auto orbit = orbits_[orbit_index];
1180 CHECK_EQ(var, orbit[0]);
1181 for (
const int other : orbit.subspan(1)) {
1182 variables->push_back(other);
1183 new_lower_bounds->push_back(lb);
1184 new_upper_bounds->push_back(ub);
1191 absl::MutexLock mutex_lock(mutex_);
1192 CHECK_EQ(domains->size(), synchronized_lower_bounds_.size());
1193 for (
int var = 0; var < domains->size(); ++var) {
1194 (*domains)[var] = (*domains)[var].IntersectionWith(
Domain(
1195 synchronized_lower_bounds_[var], synchronized_upper_bounds_[var]));
1200 absl::MutexLock mutex_lock(mutex_);
1201 if (!bounds_exported_.empty()) {
1202 std::vector<std::vector<std::string>> table;
1203 table.push_back({
"Improving bounds shared",
"Num",
"Sym"});
1204 for (
const auto& entry : bounds_exported_) {
1214 absl::MutexLock mutex_lock(mutex_);
1215 const auto it = bounds_exported_.find(worker_name);
1216 if (it == bounds_exported_.end())
return 0;
1217 return it->second.num_exported;
1221 for (
auto& buffer : clauses_by_size_) {
1224 fingerprints_.reserve(kMaxFingerprints);
1228 if (!
BlockClause(clause) || lbd > lbd_threshold_)
return false;
1229 std::vector<int>* buffer = MutableBufferForSize(clause.size());
1230 CHECK_NE(buffer,
nullptr);
1232 buffer->insert(buffer->end(), clause.begin(), clause.end());
1237 const int64_t replaced_clause_id =
1238 HashClause(clause, 1) % NumClausesOfSize(clause.size());
1239 absl::Span<int> replaced_clause = absl::MakeSpan(*buffer).subspan(
1240 replaced_clause_id * clause.size(), clause.size());
1241 dropped_literals_since_last_batch_ += clause.size();
1243 absl::c_copy(clause, replaced_clause.begin());
1250 if (clause.size() <= 2)
return false;
1252 return fingerprints_.emplace(hash).second &&
1253 !old_fingerprints_.contains(hash);
1262 std::vector<int>* buffer = MutableBufferForSize(size);
1263 while (to_fill >= size && !buffer->empty()) {
1264 batch.
Add(NextClause(size));
1268 if (to_fill < size) {
1269 dropped_literals_since_last_batch_ += buffer->size();
1274 if (fingerprints_.size() >= kMaxFingerprints / 2) {
1275 VLOG(2) <<
"Clearing fingerprints: " << fingerprints_.size() / 1024 <<
"Ki";
1276 std::swap(fingerprints_, old_fingerprints_);
1277 fingerprints_.clear();
1278 fingerprints_.reserve(kMaxFingerprints);
1282 lbd_threshold_ += 1;
1283 VLOG(2) <<
"Inc lbd: " << lbd_threshold_;
1284 }
else if (dropped_literals_since_last_batch_ > 0 &&
1286 lbd_threshold_ -= 1;
1287 VLOG(2) <<
"Dec lbd: " << lbd_threshold_;
1289 dropped_literals_since_last_batch_ = 0;
1295 for (
const auto& buffer : clauses_by_size_) {
1296 result += buffer.size();
1303 size_t hash = absl::HashOf(hash_seed, clause.size());
1304 for (
int i = 0;
i < clause.size(); ++
i) {
1305 hash ^= absl::HashOf(clause[
i], hash_seed);
1310absl::Span<const int> UniqueClauseStream::NextClause(
int size)
const {
1311 absl::Span<const int> buffer = BufferForSize(size);
1312 return buffer.subspan(buffer.size() - size, size);
1315void UniqueClauseStream::PopClause(
int size) {
1316 std::vector<int>* buffer = MutableBufferForSize(size);
1317 buffer->erase(buffer->end() - size, buffer->end());
1320int UniqueClauseStream::NumClausesOfSize(
int size)
const {
1321 return NumLiteralsOfSize(size) / size;
1325 return BufferForSize(size).size();
1329 : always_synchronize_(always_synchronize) {}
1332 bool may_terminate_early) {
1333 absl::MutexLock mutex_lock(mutex_);
1334 num_full_workers_ += may_terminate_early ? 0 : 1;
1335 const int id = id_to_last_processed_binary_clause_.size();
1336 id_to_last_processed_binary_clause_.resize(
id + 1, 0);
1337 id_to_last_returned_batch_.resize(
id + 1, -1);
1338 id_to_last_finished_batch_.resize(
id + 1, -1);
1339 id_to_num_imported_.resize(
id + 1, 0);
1340 id_to_num_exported_.resize(
id + 1, 0);
1341 id_to_worker_name_.resize(
id + 1);
1342 id_to_worker_name_[id] = worker_name;
1347 absl::MutexLock mutex_lock(mutex_);
1348 const int id = id_to_worker_name_.size();
1350 id_to_stats_.resize(
id + 1);
1351 id_to_worker_name_.resize(
id + 1);
1352 id_to_worker_name_[id] = worker_name;
1356bool SharedClausesManager::ShouldReadBatch(
int reader_id,
int writer_id) {
1357 return reader_id != writer_id;
1361 if (lit2 < lit1) std::swap(lit1, lit2);
1362 const auto p = std::make_pair(lit1, lit2);
1364 absl::MutexLock mutex_lock(mutex_);
1365 const auto [unused_it, inserted] = added_binary_clauses_set_.insert(p);
1367 added_binary_clauses_.push_back(p);
1368 if (always_synchronize_) ++last_visible_binary_clause_;
1369 id_to_num_exported_[id]++;
1373 if (id_to_last_processed_binary_clause_[
id] ==
1374 added_binary_clauses_.size() - 1) {
1375 id_to_last_processed_binary_clause_[id]++;
1381 absl::MutexLock mutex_lock(mutex_);
1382 id_to_num_exported_[id] += batch.
size();
1383 pending_batches_.push_back(std::move(batch));
1387 std::vector<absl::Span<const int>> result;
1389 absl::MutexLock mutex_lock(mutex_);
1390 id_to_last_finished_batch_[id] = id_to_last_returned_batch_[id];
1391 if (id_to_last_returned_batch_[
id] + 1 < batches_.size()) {
1392 id_to_last_returned_batch_[id] += 1;
1393 return batches_[id_to_last_returned_batch_[id]];
1398 return *empty_batch;
1402 int id, std::vector<std::pair<int, int>>* new_clauses) {
1403 new_clauses->
clear();
1404 absl::MutexLock mutex_lock(mutex_);
1405 const int last_binary_clause_seen = id_to_last_processed_binary_clause_[id];
1406 if (last_binary_clause_seen >= last_visible_binary_clause_)
return;
1408 new_clauses->assign(
1409 added_binary_clauses_.begin() + last_binary_clause_seen,
1410 added_binary_clauses_.begin() + last_visible_binary_clause_);
1411 id_to_last_processed_binary_clause_[id] = last_visible_binary_clause_;
1415 absl::MutexLock mutex_lock(mutex_);
1416 id_to_num_imported_[id] += num_imported;
1420 absl::MutexLock mutex_lock(mutex_);
1421 std::vector<std::tuple<std::string, int64_t, int64_t, int64_t, int64_t>>
1423 for (
int id = 0;
id < id_to_num_exported_.size(); ++id) {
1424 name_to_table_line.push_back(
1425 {id_to_worker_name_[id], id_to_num_exported_[id],
1426 id_to_num_imported_[id], id_to_last_processed_binary_clause_[id],
1427 last_visible_binary_clause_});
1429 if (!name_to_table_line.empty()) {
1430 absl::c_sort(name_to_table_line);
1431 std::vector<std::vector<std::string>> table;
1432 table.push_back({
"Clauses shared",
"#Exported",
"#Imported",
"#BinaryRead",
1434 for (
const auto& [name, exported, imported, binary_read, binary_total] :
1435 name_to_table_line) {
1448 absl::MutexLock mutex_lock(mutex_);
1449 absl::btree_map<std::string, Stats> name_to_table_line;
1450 for (
int id = 0;
id < id_to_stats_.size(); ++id) {
1451 const Stats stats = id_to_stats_[id];
1452 if (!stats.empty()) {
1453 name_to_table_line[id_to_worker_name_[id]] = stats;
1456 for (
int import_id = 0; import_id < import_id_to_index_.size(); ++import_id) {
1457 name_to_table_line[import_id_to_name_[import_id]].num_imported =
1458 import_id_to_num_imported_[import_id];
1460 if (!name_to_table_line.empty()) {
1461 std::vector<std::vector<std::string>> table;
1462 table.push_back({
"Linear2 shared",
"New",
"Updated",
"Imported"});
1463 for (
const auto& [name, stats] : name_to_table_line) {
1473 std::vector<CompactVectorVector<int>> batches_to_merge;
1475 absl::MutexLock mutex_lock(mutex_);
1476 last_visible_binary_clause_ = added_binary_clauses_.size();
1477 const int num_workers = id_to_last_processed_binary_clause_.size();
1478 if (num_workers <= 1)
return;
1480 if (pending_batches_.size() >= num_full_workers_) {
1481 batches_to_merge = std::move(pending_batches_);
1487 if (batches_.size() > kMinBatches) {
1488 const int min_finished_batch =
1489 std::min<int>(batches_.size() - kMinBatches,
1490 *absl::c_min_element(id_to_last_finished_batch_) + 1);
1491 for (
int i = 0;
i < min_finished_batch; ++
i) {
1492 VLOG(2) <<
"Erasing batch";
1493 batches_.pop_front();
1495 for (
int id = 0;
id < id_to_last_finished_batch_.size(); ++id) {
1496 id_to_last_returned_batch_[id] -= min_finished_batch;
1497 id_to_last_finished_batch_[id] -= min_finished_batch;
1502 if (batches_to_merge.empty())
return;
1504 for (
const auto& batch : batches_to_merge) {
1505 for (
int i = 0;
i < batch.size(); ++
i) {
1506 next_batch.
Add(batch[
i]);
1510 absl::MutexLock mutex_lock(mutex_);
1511 VLOG(2) <<
"Merging batch";
1512 batches_.push_back(next_batch.
NextBatch());
1520 absl::MutexLock mutex_lock(mutex_);
1521 auto [it, inserted] = shared_bounds_.insert({expr, {lb, ub}});
1524 id_to_stats_[id].num_new++;
1525 newly_updated_keys_.push_back(expr);
1528 auto& bounds = it->second;
1529 const bool update_lb = lb > bounds.first;
1530 if (update_lb) bounds.first = lb;
1531 const bool update_ub = ub < bounds.second;
1532 if (update_ub) bounds.second = ub;
1533 if (update_lb || update_ub) {
1534 id_to_stats_[id].num_update++;
1535 newly_updated_keys_.push_back(expr);
1541 absl::MutexLock mutex_lock(mutex_);
1542 const int import_id = import_id_to_index_.size();
1543 import_id_to_name_.push_back(name);
1544 import_id_to_index_.push_back(0);
1545 import_id_to_num_imported_.push_back(0);
1550 std::pair<SharedLinear2Bounds::Key, std::pair<IntegerValue, IntegerValue>>>
1552 std::vector<std::pair<Key, std::pair<IntegerValue, IntegerValue>>> result;
1554 absl::MutexLock mutex_lock(mutex_);
1555 MaybeCompressNewlyUpdateKeys();
1556 const int size = newly_updated_keys_.size();
1557 for (
int i = import_id_to_index_[import_id];
i < size; ++
i) {
1558 const auto& key = newly_updated_keys_[
i];
1559 result.push_back({key, shared_bounds_[key]});
1561 import_id_to_index_[import_id] = size;
1565void SharedLinear2Bounds::MaybeCompressNewlyUpdateKeys() {
1567 for (
const int index : import_id_to_index_) {
1568 min_index = std::min(index, min_index);
1570 if (min_index == 0)
return;
1572 newly_updated_keys_.erase(newly_updated_keys_.begin(),
1573 newly_updated_keys_.begin() + min_index);
1574 for (
int& index_ref : import_id_to_index_) {
1575 index_ref -= min_index;
1580 absl::Span<
const std::pair<std::string, int64_t>> stats) {
1581 absl::MutexLock mutex_lock(mutex_);
1582 for (
const auto& [key, count] : stats) {
1583 stats_[key] += count;
1588 absl::MutexLock mutex_lock(mutex_);
1589 if (stats_.empty())
return;
1591 SOLVER_LOG(logger,
"Stats across workers (summed):");
1592 std::vector<std::pair<std::string, int64_t>> to_sort_;
1593 for (
const auto& [key, count] : stats_) {
1594 to_sort_.push_back({key, count});
1596 std::sort(to_sort_.begin(), to_sort_.end());
1597 for (
const auto& [key, count] : to_sort_) {
1604 : num_subsolvers_(0),
1605 num_valid_proofs_(0),
1606 num_invalid_proofs_(0),
1607 num_unknown_proofs_(0),
1608 lrat_check_enabled_(false),
1609 drat_check_enabled_(false),
1610 num_assumed_clauses_(0),
1611 walltime_in_seconds_(0.0) {}
1614 absl::MutexLock mutex_lock(mutex_);
1615 return num_subsolvers_++;
1620 bool drat_check_enabled,
int num_assumed_clauses,
1621 double walltime_in_seconds) {
1622 absl::MutexLock mutex_lock(mutex_);
1624 num_valid_proofs_++;
1626 num_invalid_proofs_++;
1628 num_unknown_proofs_++;
1630 lrat_check_enabled_ |= lrat_check_enabled;
1631 drat_check_enabled_ |= drat_check_enabled;
1632 num_assumed_clauses_ += num_assumed_clauses;
1633 if (drat_check_enabled) {
1634 walltime_in_seconds_ += walltime_in_seconds;
1639 absl::MutexLock mutex_lock(mutex_);
1640 proof_filenames_.push_back(std::string(filename));
1644 absl::MutexLock mutex_lock(mutex_);
1645 return proof_filenames_;
1649 absl::MutexLock mutex_lock(mutex_);
1650 if (lrat_check_enabled_ || drat_check_enabled_) {
1651 if (num_valid_proofs_ == num_subsolvers_) {
1652 if (num_assumed_clauses_ > 0) {
1653 SOLVER_LOG(logger,
"LRAT_status: VALID_WITH_ASSUMED_CLAUSES");
1657 }
else if (num_invalid_proofs_ > 0) {
1662 if (drat_check_enabled_) {
1663 SOLVER_LOG(logger,
"DRAT_walltime: ", walltime_in_seconds_);
1669 if (drat_check_enabled_) {
double GetElapsedDeterministicTime() const
int Add(absl::Span< const V > values)
bool has_symmetry() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
bool has_objective() const
::operations_research::sat::IntegerVariableProto *PROTOBUF_NONNULL mutable_variables(int index)
const ::operations_research::sat::CpObjectiveProto & objective() const
double scaling_factor() const
::operations_research::sat::CpSolverSolution *PROTOBUF_NONNULL add_additional_solutions()
::int64_t num_booleans() const
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_solution()
void clear_inner_objective_lower_bound()
::int64_t num_fixed_booleans() const
void set_solution_info(Arg_ &&arg, Args_... args)
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_sufficient_assumptions_for_infeasibility()
void set_status(::operations_research::sat::CpSolverStatus value)
::operations_research::sat::CpSolverStatus status() const
void clear_best_objective_bound()
void set_inner_objective_lower_bound(::int64_t value)
void set_best_objective_bound(double value)
void clear_objective_value()
void set_objective_value(double value)
void set_gap_integral(double value)
void MergeFrom(const CpSolverResponse &from)
::int64_t solution(int index) const
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_values()
::int64_t domain(int index) const
double absolute_gap_limit() const
double relative_gap_limit() const
int NumBoundsExported(absl::string_view worker_name)
void UpdateDomains(std::vector< Domain > *domains)
void ReportPotentialNewBounds(const std::string &worker_name, absl::Span< const int > variables, absl::Span< const int64_t > new_lower_bounds, absl::Span< const int64_t > new_upper_bounds)
void LogStatistics(SolverLogger *logger)
void FixVariablesFromPartialSolution(absl::Span< const int64_t > solution, absl::Span< const int > variables_to_fix)
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
SharedBoundsManager(const CpModelProto &model_proto)
int RegisterNewId(absl::string_view worker_name, bool may_terminate_early)
void AddBinaryClause(int id, int lit1, int lit2)
void AddBatch(int id, CompactVectorVector< int > batch)
void LogStatistics(SolverLogger *logger)
void GetUnseenBinaryClauses(int id, std::vector< std::pair< int, int > > *new_clauses)
const CompactVectorVector< int > & GetUnseenClauses(int id)
SharedClausesManager(bool always_synchronize)
void NotifyNumImported(int id, int64_t num_imported)
std::vector< double > PopLast()
void AddSolution(const std::vector< double > &lp_solution)
void NewLPSolution(std::vector< double > lp_solution)
int RegisterNewImportId(std::string name)
std::vector< std::pair< Key, std::pair< IntegerValue, IntegerValue > > > NewlyUpdatedBounds(int import_id)
void LogStatistics(SolverLogger *logger)
void Add(int id, Key expr, IntegerValue lb, IntegerValue ub)
int RegisterNewId(std::string worker_name)
void NewProofFile(absl::string_view filename)
std::vector< std::string > GetProofFilenames()
void Log(SolverLogger *logger)
void NewSubsolverProofStatus(DratChecker::Status status, bool lrat_check_enabled, bool drat_check_enabled, int num_assumed_clauses, double walltime_in_seconds)
void AppendResponseToBeMerged(const CpSolverResponse &response)
void AddUnsatCore(const std::vector< int > &core)
void AddStatisticsPostprocessor(std::function< void(Model *, CpSolverResponse *)> postprocessor)
IntegerValue GetInnerObjectiveLowerBound()
void SetUpdateGapIntegralOnEachChange(bool set)
bool ProblemIsSolved() const
SharedResponseManager(Model *model)
double GapIntegral() const
void LogMessage(absl::string_view prefix, absl::string_view message)
void SetSynchronizationMode(bool always_synchronize)
int AddLogCallback(std::function< std::string(const CpSolverResponse &)> callback)
int AddBestBoundCallback(std::function< void(double)> callback)
void UnregisterLogCallback(int callback_id)
void UnregisterCallback(int callback_id)
IntegerValue BestSolutionInnerObjectiveValue()
void UnregisterBestBoundCallback(int callback_id)
void AddStatusChangeCallback(std::function< void(const SolverStatusChangeInfo &)> callback)
bool LoggingIsEnabled() const
void InitializeObjective(const CpModelProto &cp_model)
void SetGapLimitsFromParameters(const SatParameters ¶meters)
void AddFinalResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
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 FillSolveStatsInResponse(Model *model, CpSolverResponse *response)
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > NewSolution(absl::Span< const int64_t > solution_values, absl::string_view solution_info, Model *model=nullptr, int source_id=-1)
void LogMessageWithThrottling(absl::string_view prefix, absl::string_view message)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
void NotifyThatImprovingProblemIsInfeasible(absl::string_view worker_info)
void Synchronize(absl::BitGenRef random)
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > Add(SharedSolutionRepository< int64_t >::Solution solution)
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
void Log(SolverLogger *logger)
static constexpr int kMaxClauseSize
bool BlockClause(absl::Span< const int > clause)
static constexpr int kMinLbd
static size_t HashClause(absl::Span< const int > clause, size_t hash_seed=0)
static constexpr int kMaxLbd
CompactVectorVector< int > NextBatch()
int NumLiteralsOfSize(int size) const
bool Add(absl::Span< const int > clause, int lbd=2)
static constexpr int kMinClauseSize
int NumBufferedLiterals() const
static constexpr int kMaxLiteralsPerBatch
absl::Status SetTextProto(absl::string_view file_name, 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)
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
std::vector< int > GetOrbits(int n, absl::Span< const std::unique_ptr< SparsePermutation > > generators)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
int64_t ScaleInnerObjectiveValue(const CpObjectiveProto &proto, int64_t value)
std::string FormatName(absl::string_view name)
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
std::string FormatCounter(int64_t num)
ABSL_FLAG(bool, cp_model_dump_solutions, false, "DEBUG ONLY. If true, all the intermediate solution will be dumped " "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.")
#define SOLVER_LOG(logger,...)