19 #if !defined(__PORTABLE_PLATFORM__)
24 #include "absl/container/flat_hash_set.h"
25 #include "absl/random/random.h"
38 "DEBUG ONLY. If true, all the intermediate solution will be dumped "
39 "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.");
42 std::string, cp_model_load_debug_solution,
"",
43 "DEBUG ONLY. When this is set to a non-empty file name, "
44 "we will interpret this as an internal solution which can be used for "
45 "debugging. For instance we use it to identify wrong cuts/reasons.");
54 if (
response.solution().empty())
return;
58 solution.variable_values.assign(
response.solution().begin(),
65 solution.rank = -
response.best_objective_bound();
71 std::vector<double> lp_solution) {
72 if (lp_solution.empty())
return;
76 solution.variable_values = std::move(lp_solution);
79 absl::MutexLock mutex_lock(&
mutex_);
80 solution.rank = -num_synchronization_;
85 absl::MutexLock mutex_lock(&mutex_);
86 return !solutions_.empty();
90 absl::MutexLock mutex_lock(&mutex_);
91 std::vector<double> solution;
92 if (solutions_.empty())
return solution;
94 solution = std::move(solutions_.back());
95 solutions_.pop_back();
100 const std::vector<double>& lp_solution) {
101 absl::MutexLock mutex_lock(&mutex_);
102 solutions_.push_back(lp_solution);
107 const CpModelProto*
proto,
111 : enumerate_all_solutions_(enumerate_all_solutions),
112 model_proto_(*
proto),
114 shared_time_limit_(shared_time_limit),
120 std::string ProgressMessage(
const std::string& event_or_solution_count,
121 double time_in_seconds,
double obj_best,
122 double obj_lb,
double obj_ub,
123 const std::string& solution_info) {
124 const std::string obj_next =
125 absl::StrFormat(
"next:[%.9g,%.9g]", obj_lb, obj_ub);
126 return absl::StrFormat(
"#%-5s %6.2fs best:%-5.9g %-15s %s",
127 event_or_solution_count, time_in_seconds, obj_best,
128 obj_next, solution_info);
131 std::string SatProgressMessage(
const std::string& event_or_solution_count,
132 double time_in_seconds,
133 const std::string& solution_info) {
134 return absl::StrFormat(
"#%-5s %6.2fs %s", event_or_solution_count,
135 time_in_seconds, solution_info);
141 absl::MutexLock mutex_lock(&mutex_);
142 update_integral_on_each_change_ = set;
146 absl::MutexLock mutex_lock(&mutex_);
147 UpdatePrimalIntegralInternal();
150 void SharedResponseManager::UpdatePrimalIntegralInternal() {
151 if (!model_proto_.has_objective())
return;
154 const double time_delta = current_time - last_primal_integral_time_stamp_;
161 const CpObjectiveProto& obj = model_proto_.objective();
162 const double factor =
163 obj.scaling_factor() != 0.0 ? std::abs(obj.scaling_factor()) : 1.0;
164 const double bounds_delta = std::log(1 + factor * last_absolute_gap_);
165 primal_integral_ += time_delta * bounds_delta;
168 last_primal_integral_time_stamp_ = current_time;
170 std::max(0.0,
static_cast<double>(inner_objective_upper_bound_) -
171 static_cast<double>(inner_objective_lower_bound_));
176 absl::MutexLock mutex_lock(&mutex_);
177 if (!model_proto_.has_objective())
return;
178 absolute_gap_limit_ =
parameters.absolute_gap_limit();
179 relative_gap_limit_ =
parameters.relative_gap_limit();
182 void SharedResponseManager::TestGapLimitsIfNeeded() {
186 if (update_integral_on_each_change_) UpdatePrimalIntegralInternal();
188 if (absolute_gap_limit_ == 0 && relative_gap_limit_ == 0)
return;
192 const CpObjectiveProto& obj = model_proto_.objective();
193 const double user_best =
195 const double user_bound =
197 const double gap = std::abs(user_best - user_bound);
198 if (gap <= absolute_gap_limit_) {
199 SOLVER_LOG(logger_,
"Absolute gap limit of ", absolute_gap_limit_,
206 shared_time_limit_->
Stop();
208 if (gap /
std::max(1.0, std::abs(user_best)) < relative_gap_limit_) {
209 SOLVER_LOG(logger_,
"Relative gap limit of ", relative_gap_limit_,
214 shared_time_limit_->
Stop();
219 const std::string& update_info, IntegerValue lb, IntegerValue ub) {
220 absl::MutexLock mutex_lock(&mutex_);
221 CHECK(model_proto_.has_objective());
228 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
233 (lb > inner_objective_lower_bound_ || ub < inner_objective_upper_bound_);
234 if (lb > inner_objective_lower_bound_) {
239 DCHECK_LE(inner_objective_upper_bound_, best_solution_objective_value_);
240 inner_objective_lower_bound_ =
241 std::min(best_solution_objective_value_, lb.value());
243 if (ub < inner_objective_upper_bound_) {
244 inner_objective_upper_bound_ = ub.value();
246 if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
253 if (update_integral_on_each_change_) UpdatePrimalIntegralInternal();
255 SatProgressMessage(
"Done", wall_timer_.
Get(), update_info));
259 const CpObjectiveProto& obj = model_proto_.objective();
264 if (model_proto_.objective().scaling_factor() < 0) {
267 RegisterObjectiveBoundImprovement(update_info);
268 SOLVER_LOG(logger_, ProgressMessage(
"Bound", wall_timer_.
Get(), best,
269 new_lb, new_ub, update_info));
271 if (change) TestGapLimitsIfNeeded();
278 const std::string& worker_info) {
279 absl::MutexLock mutex_lock(&mutex_);
285 if (!model_proto_.has_objective()) {
286 best_response_.set_all_solutions_were_found(
true);
291 inner_objective_lower_bound_ = best_solution_objective_value_;
292 if (update_integral_on_each_change_) UpdatePrimalIntegralInternal();
298 SatProgressMessage(
"Done", wall_timer_.
Get(), worker_info));
302 absl::MutexLock mutex_lock(&mutex_);
303 best_response_.clear_sufficient_assumptions_for_infeasibility();
304 for (
const int ref : core) {
305 best_response_.add_sufficient_assumptions_for_infeasibility(ref);
310 absl::MutexLock mutex_lock(&mutex_);
311 return IntegerValue(inner_objective_lower_bound_);
315 absl::MutexLock mutex_lock(&mutex_);
316 return IntegerValue(inner_objective_upper_bound_);
320 absl::MutexLock mutex_lock(&mutex_);
321 synchronized_inner_objective_lower_bound_ =
322 IntegerValue(inner_objective_lower_bound_);
323 synchronized_inner_objective_upper_bound_ =
324 IntegerValue(inner_objective_upper_bound_);
328 absl::MutexLock mutex_lock(&mutex_);
329 return synchronized_inner_objective_lower_bound_;
333 absl::MutexLock mutex_lock(&mutex_);
334 return synchronized_inner_objective_upper_bound_;
338 absl::MutexLock mutex_lock(&mutex_);
339 return IntegerValue(best_solution_objective_value_);
343 absl::MutexLock mutex_lock(&mutex_);
344 return primal_integral_;
348 std::function<
void(
const CpSolverResponse&)>
callback) {
349 absl::MutexLock mutex_lock(&mutex_);
350 const int id = next_callback_id_++;
351 callbacks_.emplace_back(
id, std::move(
callback));
356 absl::MutexLock mutex_lock(&mutex_);
357 for (
int i = 0; i < callbacks_.size(); ++i) {
358 if (callbacks_[i].first == callback_id) {
359 callbacks_.erase(callbacks_.begin() + i);
363 LOG(DFATAL) <<
"Callback id " << callback_id <<
" not registered.";
367 absl::MutexLock mutex_lock(&mutex_);
368 FillObjectiveValuesInBestResponse();
369 return best_response_;
372 void SharedResponseManager::FillObjectiveValuesInBestResponse() {
373 if (!model_proto_.has_objective())
return;
374 const CpObjectiveProto& obj = model_proto_.objective();
377 best_response_.clear_objective_value();
378 best_response_.clear_best_objective_bound();
385 best_response_.set_objective_value(
388 best_response_.set_objective_value(
393 best_response_.set_best_objective_bound(
397 best_response_.set_primal_integral(primal_integral_);
402 absl::MutexLock mutex_lock(&mutex_);
404 if (model_proto_.has_objective()) {
405 const int64_t objective_value =
411 solution.variable_values.assign(
response.solution().begin(),
413 solution.rank = objective_value;
414 solutions_.
Add(solution);
418 if (objective_value > inner_objective_upper_bound_)
return;
424 DCHECK_GE(objective_value, inner_objective_lower_bound_);
426 DCHECK_LT(objective_value, best_solution_objective_value_);
427 best_solution_objective_value_ = objective_value;
430 inner_objective_upper_bound_ = objective_value - 1;
435 if (!model_proto_.has_objective() && !enumerate_all_solutions_) {
441 best_response_.set_solution_info(
response.solution_info());
442 *best_response_.mutable_solution() =
response.solution();
443 *best_response_.mutable_solution_lower_bounds() =
445 *best_response_.mutable_solution_upper_bounds() =
449 if (model_proto_.has_objective() &&
450 inner_objective_lower_bound_ > inner_objective_upper_bound_) {
457 std::string solution_info =
response.solution_info();
458 if (
model !=
nullptr) {
460 const int64_t num_fixed =
model->Get<
SatSolver>()->NumFixedVariables();
461 absl::StrAppend(&solution_info,
" fixed_bools:", num_fixed,
"/",
465 if (model_proto_.has_objective()) {
466 const CpObjectiveProto& obj = model_proto_.objective();
471 if (model_proto_.objective().scaling_factor() < 0) {
474 RegisterSolutionFound(solution_info);
475 SOLVER_LOG(logger_, ProgressMessage(absl::StrCat(num_solutions_),
476 wall_timer_.
Get(), best, lb, ub,
479 SOLVER_LOG(logger_, SatProgressMessage(absl::StrCat(num_solutions_),
480 wall_timer_.
Get(), solution_info));
486 TestGapLimitsIfNeeded();
487 if (!callbacks_.empty()) {
488 FillObjectiveValuesInBestResponse();
489 SetStatsFromModelInternal(
model);
490 for (
const auto& pair : callbacks_) {
491 pair.second(best_response_);
495 #if !defined(__PORTABLE_PLATFORM__)
498 if (absl::GetFlag(FLAGS_cp_model_dump_solutions)) {
499 const std::string
file =
500 absl::StrCat(dump_prefix_,
"solution_", num_solutions_,
".pbtxt");
501 LOG(
INFO) <<
"Dumping solution to '" <<
file <<
"'.";
508 #if !defined(__PORTABLE_PLATFORM__)
509 if (absl::GetFlag(FLAGS_cp_model_load_debug_solution).empty())
return;
513 LOG(
INFO) <<
"Reading solution from '"
514 << absl::GetFlag(FLAGS_cp_model_load_debug_solution) <<
"'.";
520 debug_solution.resize(
522 for (
int i = 0; i <
response.solution().size(); ++i) {
523 if (!mapping.IsInteger(i))
continue;
524 const IntegerVariable
var = mapping.Integer(i);
532 if (objective_def ==
nullptr)
return;
534 const IntegerVariable objective_var = objective_def->
objective_var;
535 const int64_t objective_value =
537 debug_solution[objective_var] = objective_value;
538 debug_solution[
NegationOf(objective_var)] = -objective_value;
543 absl::MutexLock mutex_lock(&mutex_);
544 SetStatsFromModelInternal(
model);
547 void SharedResponseManager::SetStatsFromModelInternal(
Model*
model) {
548 if (
model ==
nullptr)
return;
551 best_response_.set_num_booleans(sat_solver->NumVariables());
552 best_response_.set_num_branches(sat_solver->num_branches());
553 best_response_.set_num_conflicts(sat_solver->num_failures());
554 best_response_.set_num_binary_propagations(sat_solver->num_propagations());
555 best_response_.set_num_restarts(sat_solver->num_restarts());
556 best_response_.set_num_integer_propagations(
557 integer_trail ==
nullptr ? 0 : integer_trail->num_enqueues());
559 best_response_.set_wall_time(
time_limit->GetElapsedTime());
560 best_response_.set_deterministic_time(
563 int64_t num_lp_iters = 0;
566 num_lp_iters += lp->total_num_simplex_iterations();
568 best_response_.set_num_lp_iterations(num_lp_iters);
572 absl::MutexLock mutex_lock(&mutex_);
578 if (improvement_info.empty())
return "";
580 std::string worker_name = improvement_info;
583 const auto& hint_suffix = worker_name.find(
" [");
584 if (hint_suffix != std::string::npos) {
585 worker_name.erase(hint_suffix);
589 const auto& lns_suffix = worker_name.find(
'(');
590 if (lns_suffix != std::string::npos) {
591 worker_name.erase(lns_suffix);
595 const auto fixed_suffix = worker_name.find(
" fixed_bools:");
596 if (fixed_suffix != std::string::npos) {
597 worker_name.erase(fixed_suffix);
603 void SharedResponseManager::RegisterSolutionFound(
604 const std::string& improvement_info) {
605 if (improvement_info.empty())
return;
609 void SharedResponseManager::RegisterObjectiveBoundImprovement(
610 const std::string& improvement_info) {
611 if (improvement_info.empty() || improvement_info ==
"initial domain")
return;
616 absl::MutexLock mutex_lock(&mutex_);
617 if (!primal_improvements_count_.empty()) {
618 SOLVER_LOG(logger_,
"Solutions found per subsolver:");
619 for (
const auto& entry : primal_improvements_count_) {
620 SOLVER_LOG(logger_,
" '", entry.first,
"': ", entry.second);
623 if (!dual_improvements_count_.empty()) {
624 SOLVER_LOG(logger_,
"Objective bounds found per subsolver:");
625 for (
const auto& entry : dual_improvements_count_) {
626 SOLVER_LOG(logger_,
" '", entry.first,
"': ", entry.second);
634 lower_bounds_(num_variables_, std::numeric_limits<int64_t>::
min()),
635 upper_bounds_(num_variables_, std::numeric_limits<int64_t>::
max()),
636 synchronized_lower_bounds_(num_variables_,
637 std::numeric_limits<int64_t>::
min()),
638 synchronized_upper_bounds_(num_variables_,
639 std::numeric_limits<int64_t>::
max()) {
640 changed_variables_since_last_synchronize_.ClearAndResize(num_variables_);
641 for (
int i = 0; i < num_variables_; ++i) {
642 lower_bounds_[i] =
model_proto.variables(i).domain(0);
643 const int domain_size =
model_proto.variables(i).domain_size();
644 upper_bounds_[i] =
model_proto.variables(i).domain(domain_size - 1);
645 synchronized_lower_bounds_[i] = lower_bounds_[i];
646 synchronized_upper_bounds_[i] = upper_bounds_[i];
651 const CpModelProto&
model_proto,
const std::string& worker_name,
652 const std::vector<int>& variables,
653 const std::vector<int64_t>& new_lower_bounds,
654 const std::vector<int64_t>& new_upper_bounds) {
655 CHECK_EQ(variables.size(), new_lower_bounds.size());
656 CHECK_EQ(variables.size(), new_upper_bounds.size());
657 int num_improvements = 0;
659 absl::MutexLock mutex_lock(&mutex_);
660 for (
int i = 0; i < variables.size(); ++i) {
661 const int var = variables[i];
662 if (
var >= num_variables_)
continue;
663 const int64_t old_lb = lower_bounds_[
var];
664 const int64_t old_ub = upper_bounds_[
var];
665 const int64_t new_lb = new_lower_bounds[i];
666 const int64_t new_ub = new_upper_bounds[i];
667 const bool changed_lb = new_lb > old_lb;
668 const bool changed_ub = new_ub < old_ub;
670 if (!changed_lb && !changed_ub)
continue;
673 lower_bounds_[
var] = new_lb;
676 upper_bounds_[
var] = new_ub;
678 changed_variables_since_last_synchronize_.Set(
var);
683 if (num_improvements > 0) {
684 VLOG(2) << worker_name <<
" exports " << num_improvements
690 absl::MutexLock mutex_lock(&mutex_);
692 changed_variables_since_last_synchronize_.PositionsSetAtLeastOnce()) {
693 synchronized_lower_bounds_[
var] = lower_bounds_[
var];
694 synchronized_upper_bounds_[
var] = upper_bounds_[
var];
695 for (
int j = 0; j < id_to_changed_variables_.size(); ++j) {
696 id_to_changed_variables_[j].Set(
var);
699 changed_variables_since_last_synchronize_.ClearAll();
703 absl::MutexLock mutex_lock(&mutex_);
704 const int id = id_to_changed_variables_.size();
705 id_to_changed_variables_.resize(
id + 1);
706 id_to_changed_variables_[id].ClearAndResize(num_variables_);
707 for (
int var = 0;
var < num_variables_; ++
var) {
708 const int64_t lb = model_proto_.variables(
var).domain(0);
709 const int domain_size = model_proto_.variables(
var).domain_size();
710 const int64_t ub = model_proto_.variables(
var).domain(domain_size - 1);
711 if (lb != synchronized_lower_bounds_[
var] ||
712 ub != synchronized_upper_bounds_[
var]) {
713 id_to_changed_variables_[id].Set(
var);
720 int id, std::vector<int>* variables, std::vector<int64_t>* new_lower_bounds,
721 std::vector<int64_t>* new_upper_bounds) {
723 new_lower_bounds->clear();
724 new_upper_bounds->clear();
726 absl::MutexLock mutex_lock(&mutex_);
727 for (
const int var : id_to_changed_variables_[
id].PositionsSetAtLeastOnce()) {
728 variables->push_back(
var);
729 new_lower_bounds->push_back(synchronized_lower_bounds_[
var]);
730 new_upper_bounds->push_back(synchronized_upper_bounds_[
var]);
732 id_to_changed_variables_[id].ClearAll();
#define DCHECK_LE(val1, val2)
#define CHECK_EQ(val1, val2)
#define CHECK_GE(val1, val2)
#define DCHECK_GE(val1, val2)
#define DCHECK_LT(val1, val2)
#define VLOG(verboselevel)
double GetElapsedDeterministicTime() const
bool LoggingIsEnabled() const
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
IntegerVariable NumIntegerVariables() const
Class that owns everything related to a particular optimization model.
SharedBoundsManager(const CpModelProto &model_proto)
void ReportPotentialNewBounds(const CpModelProto &model_proto, 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)
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
void AddNewSolution(const std::vector< double > &lp_solution)
std::vector< double > GetNewSolution()
bool HasNewSolution() const
void NewLPSolution(std::vector< double > lp_solution)
void NewRelaxationSolution(const CpSolverResponse &response)
bool ProblemIsSolved() const
CpSolverResponse GetResponse()
void SetStatsFromModel(Model *model)
double PrimalIntegral() const
void UpdatePrimalIntegral()
SharedResponseManager(bool enumerate_all_solutions, const CpModelProto *proto, const WallTimer *wall_timer, SharedTimeLimit *shared_time_limit, SolverLogger *logger)
IntegerValue GetInnerObjectiveUpperBound()
IntegerValue SynchronizedInnerObjectiveUpperBound()
IntegerValue SynchronizedInnerObjectiveLowerBound()
void NewSolution(const CpSolverResponse &response, Model *model)
void DisplayImprovementStatistics()
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
IntegerValue BestSolutionInnerObjectiveValue()
void AddUnsatCore(const std::vector< int > &core)
void SetGapLimitsFromParameters(const SatParameters ¶meters)
int AddSolutionCallback(std::function< void(const CpSolverResponse &)> callback)
void SetUpdatePrimalIntegralOnEachChange(bool set)
void LoadDebugSolution(Model *)
IntegerValue GetInnerObjectiveLowerBound()
void UnregisterCallback(int callback_id)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
void Add(const Solution &solution)
void AddInternal(const Solution &solution) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_)
CpModelProto const * model_proto
SharedResponseManager * response
SharedTimeLimit * time_limit
absl::Status GetTextProto(const absl::string_view &filename, google::protobuf::Message *proto, int flags)
absl::Status SetTextProto(const absl::string_view &filename, const google::protobuf::Message &proto, int flags)
int NumVariables(const VariablesProto &variables)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, const CpSolverResponse &response)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
std::string ExtractWorkerName(const std::string &improvement_info)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Collection of objects used to extend the Constraint Solver library.
IntegerVariable objective_var
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,...)