24#include "absl/base/thread_annotations.h"
25#include "absl/flags/flag.h"
26#include "absl/log/check.h"
27#include "absl/log/log.h"
28#include "absl/log/vlog_is_on.h"
29#include "absl/random/distributions.h"
30#include "absl/strings/str_cat.h"
31#include "absl/synchronization/mutex.h"
32#include "google/protobuf/arena.h"
55 local_params_(local_parameters),
62 shared_->stat_tables->AddTimingStat(*
this);
66 if (shared_->SearchIsDone())
return false;
69 absl::MutexLock mutex_lock(mutex_);
70 return !task_in_flight_;
75 absl::MutexLock mutex_lock(mutex_);
76 stop_current_chunk_.store(
false);
77 task_in_flight_ =
true;
78 objective_lb_ = shared_->response->GetInnerObjectiveLowerBound();
79 objective_ub_ = shared_->response->GetInnerObjectiveUpperBound();
81 return [
this, task_id]() {
82 if (ResetAndSolveModel(task_id)) {
88 std::vector<int64_t> solution_values(local_response.
solution().begin(),
90 if (local_params_.cp_model_presolve()) {
91 const int num_original_vars = shared_->model_proto.variables_size();
93 mapping_proto_, postsolve_mapping_,
96 shared_->response->NewSolution(solution_values, Info());
98 absl::MutexLock mutex_lock(mutex_);
99 shared_->response->UpdateInnerObjectiveBounds(
100 Info(), current_objective_target_ub_ + 1, objective_ub_);
104 absl::MutexLock mutex_lock(mutex_);
105 task_in_flight_ =
false;
106 if (local_sat_model_ !=
nullptr) {
107 const double dtime = local_sat_model_->GetOrCreate<
TimeLimit>()
108 ->GetElapsedDeterministicTime();
110 shared_->time_limit->AdvanceDeterministicTime(dtime);
116 absl::MutexLock mutex_lock(mutex_);
117 if (!task_in_flight_)
return;
121 if (stop_current_chunk_)
return;
125 if (shared_->SearchIsDone()) {
126 stop_current_chunk_.store(
true);
130 if (shared_->response->GetInnerObjectiveLowerBound() > objective_lb_) {
131 stop_current_chunk_.store(
true);
136 if (shared_->response->GetInnerObjectiveUpperBound() <=
137 current_objective_target_ub_ &&
138 current_objective_target_ub_ != objective_lb_) {
139 stop_current_chunk_.store(
true);
144 if (current_objective_target_ub_ != objective_lb_ &&
145 shared_->response->GetInnerObjectiveUpperBound() -
146 shared_->response->GetInnerObjectiveLowerBound() <=
147 local_params_.shaving_search_threshold()) {
148 stop_current_chunk_.store(
true);
152std::string ObjectiveShavingSolver::Info() {
153 return absl::StrCat(
name(),
" (vars=", local_proto_->
variables().size(),
154 " csts=", local_proto_->
constraints().size(),
")");
157void ObjectiveShavingSolver::ResetModel() {
159 arena_ = std::make_unique<google::protobuf::Arena>(
160 google::protobuf::ArenaOptions(
161 {.start_block_size =
static_cast<size_t>(
164 arena_ = std::make_unique<google::protobuf::Arena>();
166 local_proto_ = google::protobuf::Arena::Create<CpModelProto>(arena_.get());
167 *local_proto_ = shared_->model_proto;
170bool ObjectiveShavingSolver::ResetAndSolveModel(int64_t task_id) {
171 local_sat_model_ = std::make_unique<Model>(
name());
172 *local_sat_model_->GetOrCreate<SatParameters>() = local_params_;
173 local_sat_model_->GetOrCreate<SatParameters>()->set_random_seed(
174 CombineSeed(local_params_.random_seed(), task_id));
176 auto* time_limit = local_sat_model_->GetOrCreate<TimeLimit>();
177 shared_->time_limit->UpdateLocalLimit(time_limit);
178 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
180 auto* random = local_sat_model_->GetOrCreate<ModelRandomGenerator>();
184 *local_proto_->mutable_variables() =
185 helper_->FullNeighborhood().delta.variables();
188 IntegerValue objective_lb;
189 IntegerValue chosen_objective_ub;
191 absl::MutexLock mutex_lock(mutex_);
192 objective_lb = objective_lb_;
193 if (objective_ub_ - objective_lb <=
194 local_params_.shaving_search_threshold()) {
195 current_objective_target_ub_ = objective_lb;
197 const IntegerValue mid = (objective_ub_ - objective_lb) / 2;
198 current_objective_target_ub_ =
199 objective_lb + absl::LogUniform<int64_t>(*random, 0, mid.value());
201 chosen_objective_ub = current_objective_target_ub_;
202 VLOG(2) <<
name() <<
": from [" << objective_lb.value() <<
".."
203 << objective_ub_.value() <<
"] <= " << chosen_objective_ub.value();
209 Domain obj_domain = Domain(objective_lb.value(), chosen_objective_ub.value());
210 if (local_proto_->objective().domain_size() > 1) {
212 obj_domain = obj_domain.IntersectionWith(
213 Domain(local_proto_->objective().domain(0),
214 local_proto_->objective().domain(1)));
216 if (local_proto_->objective().vars().size() == 1 &&
217 local_proto_->objective().coeffs(0) == 1) {
219 local_proto_->mutable_variables(local_proto_->objective().vars(0));
220 const Domain reduced_var_domain = obj_domain.IntersectionWith(
221 Domain(obj_var->domain(0), obj_var->domain(1)));
222 if (reduced_var_domain.IsEmpty()) {
227 auto* obj = local_proto_->add_constraints()->mutable_linear();
228 *obj->mutable_vars() = local_proto_->objective().vars();
229 *obj->mutable_coeffs() = local_proto_->objective().coeffs();
230 if (obj_domain.IsEmpty()) {
237 local_proto_->clear_objective();
238 local_proto_->set_name(absl::StrCat(local_proto_->name(),
"_obj_shaving_",
239 objective_lb.value()));
242 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
243 const std::string
name =
244 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
245 "objective_shaving_", objective_lb.value(),
".pb.txt");
246 LOG(INFO) <<
"Dumping objective shaving model to '" <<
name <<
"'.";
251 if (local_params_.cp_model_presolve()) {
252 mapping_proto_.Clear();
253 postsolve_mapping_.clear();
254 auto context = std::make_unique<PresolveContext>(
255 local_sat_model_.get(), local_proto_, &mapping_proto_);
259 absl::MutexLock mutex_lock(mutex_);
260 shared_->response->UpdateInnerObjectiveBounds(
273 if (time_limit->LimitReached())
return false;
275 LoadCpModel(*local_proto_, local_sat_model_.get());
283 local_params_(local_parameters),
285 stop_current_chunk_(false),
286 model_proto_(shared->model_proto) {
287 if (shared_->bounds !=
nullptr) {
288 shared_bounds_id_ = shared_->bounds->RegisterNewId();
291 absl::MutexLock mutex_lock(mutex_);
293 var_domains_.push_back(ReadDomainFromProto(var_proto));
298 shared_->stat_tables->AddTimingStat(*
this);
300 if (!VLOG_IS_ON(1))
return;
301 if (shared_ ==
nullptr || shared_->stats ==
nullptr)
return;
302 std::vector<std::pair<std::string, int64_t>> stats;
303 absl::MutexLock mutex_lock(mutex_);
304 stats.push_back({
"variable_shaving/num_vars_tried", num_vars_tried_});
305 stats.push_back({
"variable_shaving/num_vars_shaved", num_vars_shaved_});
307 {
"variable_shaving/num_infeasible_found", num_infeasible_found_});
308 shared_->stats->AddStats(stats);
312 return !shared_->SearchIsDone();
317 if (shared_->bounds ==
nullptr)
return;
322 absl::MutexLock lock(mutex_);
325 const int64_t lb = obj_lb;
326 if (lb > domain.
Min()) {
328 shared_->bounds->ReportPotentialNewBounds(
name(), {state.
var_index},
329 {lb}, {domain.
Max()});
330 VLOG(1) <<
name() <<
": var(" << state.
var_index <<
") " << domain
334 const int64_t ub = -obj_lb;
335 if (ub < domain.
Max()) {
337 shared_->bounds->ReportPotentialNewBounds(
name(), {state.
var_index},
338 {domain.
Min()}, {ub});
339 VLOG(1) <<
name() <<
": var(" << state.
var_index <<
") " << domain
347 absl::MutexLock lock(mutex_);
349 Domain new_domain = domain;
350 ++num_infeasible_found_;
352 VLOG(1) <<
name() <<
": var(" << state.
var_index <<
") " << domain <<
" ==> "
355 if (domain != new_domain) {
358 shared_->bounds->ReportPotentialNewBounds(
361 var_domains_[state.
var_index] = new_domain;
362 if (var_domains_[state.
var_index].IsEmpty()) {
363 shared_->response->NotifyThatImprovingProblemIsInfeasible(
364 "Unsat during variables shaving");
371 return [
this, task_id]()
mutable {
372 Model local_sat_model;
375 if (ResetAndSolveModel(task_id, &state, &local_sat_model, &shaving_proto)) {
381 absl::MutexLock mutex_lock(mutex_);
385 shared_->time_limit->AdvanceDeterministicTime(dtime);
390 absl::MutexLock mutex_lock(mutex_);
393 if (stop_current_chunk_)
return;
395 if (shared_->SearchIsDone()) {
396 stop_current_chunk_.store(
true);
399 if (shared_->bounds !=
nullptr) {
400 std::vector<int> model_variables;
401 std::vector<int64_t> new_lower_bounds;
402 std::vector<int64_t> new_upper_bounds;
403 shared_->bounds->GetChangedBounds(shared_bounds_id_, &model_variables,
404 &new_lower_bounds, &new_upper_bounds);
406 for (
int i = 0;
i < model_variables.size(); ++
i) {
407 const int var = model_variables[
i];
408 const int64_t new_lb = new_lower_bounds[
i];
409 const int64_t new_ub = new_upper_bounds[
i];
410 const Domain& old_domain = var_domains_[var];
414 shared_->response->NotifyThatImprovingProblemIsInfeasible(
415 "Unsat during variables shaving");
418 var_domains_[var] = new_domain;
423std::string VariablesShavingSolver::Info() {
424 return absl::StrCat(
name(),
" (vars=", model_proto_.
variables().size(),
428int64_t VariablesShavingSolver::DomainSize(
int var)
const
429 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
430 return var_domains_[var].Size();
433bool VariablesShavingSolver::VarIsFixed(
int int_var)
const
434 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
435 return var_domains_[int_var].IsFixed();
438bool VariablesShavingSolver::ConstraintIsInactive(
int c)
const
439 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
440 for (
const int ref : model_proto_.constraints(c).enforcement_literal()) {
442 if (VarIsFixed(var) && var_domains_[var].Min() == (var == ref ? 0 : 1)) {
449bool VariablesShavingSolver::FindNextVar(State* state)
450 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
451 const int num_vars = var_domains_.size();
455 if (model_proto_.has_objective()) {
456 const int num_obj_vars = model_proto_.objective().vars().size();
457 if (num_obj_vars > 1) {
458 while (current_index_ < num_obj_vars) {
459 const int var = model_proto_.objective().vars(current_index_);
460 if (VarIsFixed(var)) {
464 state->var_index = var;
465 state->minimize = model_proto_.objective().coeffs(current_index_) > 0;
466 state->shave_using_objective =
true;
475 for (
int i = 0;
i < num_vars; ++
i) {
476 const int var = (current_index_ / 2 +
i) % num_vars;
477 if (VarIsFixed(var)) {
484 if (model_proto_.has_objective() &&
485 model_proto_.objective().vars_size() == 1 &&
486 var == model_proto_.objective().vars(0)) {
490 state->var_index = var;
491 state->minimize = current_index_ % 2 == 0;
492 state->shave_using_objective = current_index_ / num_vars < 2;
498void VariablesShavingSolver::CopyModelConnectedToVar(
500 bool* has_no_overlap_2d) ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
501 auto var_to_node = [](
int var) {
return var; };
502 auto ct_to_node = [num_vars = model_proto_.variables_size()](
int c) {
507 const int root_index = var_to_node(state->var_index);
508 std::vector<int> ignored_constraints;
515 DenseConnectedComponentsFinder cc_finder;
517 model_proto_.variables_size());
518 for (
int c = 0;
c < model_proto_.constraints_size(); ++
c) {
519 if (ConstraintIsInactive(c))
continue;
524 *has_no_overlap_2d =
true;
525 const int num_intervals = ct.no_overlap_2d().x_intervals().size();
526 for (
int i = 0;
i < num_intervals; ++
i) {
527 const int x_interval = ct.no_overlap_2d().x_intervals(
i);
528 const int y_interval = ct.no_overlap_2d().y_intervals(
i);
529 cc_finder.
AddEdge(ct_to_node(x_interval), ct_to_node(y_interval));
531 ignored_constraints.push_back(c);
535 const int ct_node = ct_to_node(c);
537 if (VarIsFixed(var))
continue;
538 cc_finder.
AddEdge(ct_node, var_to_node(var));
541 cc_finder.
AddEdge(ct_node, ct_to_node(interval));
545 DCHECK(shaving_proto->variables().empty());
546 DCHECK(shaving_proto->constraints().empty());
548 const auto active_constraints = [&cc_finder, root_index, &ct_to_node](
int c) {
549 return cc_finder.
Connected(root_index, ct_to_node(c));
553 std::vector<int> interval_mapping;
555 model_proto_, var_domains_, active_constraints, &context,
559 for (
const int c : ignored_constraints) {
560 CHECK(!active_constraints(c));
566 shaving_proto->add_constraints()->mutable_no_overlap_2d();
567 const int num_intervals = ct.no_overlap_2d().
x_intervals().size();
568 for (
int i = 0;
i < num_intervals; ++
i) {
569 const int x_interval = ct.no_overlap_2d().x_intervals(
i);
570 const int y_interval = ct.no_overlap_2d().y_intervals(
i);
571 if (!active_constraints(x_interval))
continue;
572 if (!active_constraints(y_interval))
continue;
574 new_no_overlap_2d->add_x_intervals(interval_mapping[x_interval]);
575 new_no_overlap_2d->add_y_intervals(interval_mapping[y_interval]);
581 int num_active_variables = 0;
582 for (
int i = 0;
i < model_proto_.variables().size(); ++
i) {
583 if (cc_finder.
Connected(root_index, var_to_node(
i))) {
584 ++num_active_variables;
587 int num_active_constraints = 0;
588 for (
int c = 0;
c < model_proto_.constraints().size(); ++
c) {
589 if (cc_finder.
Connected(root_index, ct_to_node(c))) {
590 ++num_active_constraints;
594 LOG(INFO) <<
"#shaving_constraints:" << shaving_proto->constraints_size()
595 <<
" #active_constraints:" << num_active_constraints <<
"/"
596 << model_proto_.constraints_size()
597 <<
" #active_variables:" << num_active_variables <<
"/"
598 << model_proto_.variables_size();
601 shaving_proto->clear_objective();
603 if (state->shave_using_objective) {
604 if (state->minimize) {
605 shaving_proto->mutable_objective()->add_vars(state->var_index);
606 shaving_proto->mutable_objective()->add_coeffs(1);
608 shaving_proto->mutable_objective()->add_vars(state->var_index);
609 shaving_proto->mutable_objective()->add_coeffs(-1);
612 const Domain domain =
616 if (domain.Size() > local_params_.shaving_search_threshold()) {
617 const int64_t mid_range = (domain.
Max() - domain.
Min()) / 2;
619 delta = absl::LogUniform<int64_t>(*random, 0, mid_range);
622 if (state->minimize) {
623 state->reduced_domain =
624 domain.IntersectionWith({domain.
Min(), domain.
Min() + delta});
626 state->reduced_domain =
627 domain.IntersectionWith({domain.
Max() - delta, domain.
Max()});
630 shaving_proto->mutable_variables(state->var_index));
634bool VariablesShavingSolver::ResetAndSolveModel(int64_t task_id, State* state,
637 SatParameters* params = local_model->GetOrCreate<SatParameters>();
638 *params = local_params_;
639 params->set_random_seed(
CombineSeed(local_params_.random_seed(), task_id));
641 bool has_no_overlap_2d =
false;
643 absl::MutexLock lock(mutex_);
644 if (!FindNextVar(state))
return false;
645 CopyModelConnectedToVar(state, local_model, shaving_proto,
652 auto sol = shared_->response->SolutionPool().BestSolutions().GetSolution(0);
653 if (sol !=
nullptr) {
654 const std::vector<int64_t>&
solution = sol->variable_values;
655 auto* hint = shaving_proto->mutable_solution_hint();
657 hint->clear_values();
658 for (
int var = 0; var <
solution.size(); ++var) {
665 auto* time_limit = local_model->GetOrCreate<TimeLimit>();
666 shared_->time_limit->UpdateLocalLimit(time_limit);
667 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
668 time_limit->ChangeDeterministicLimit(
669 time_limit->GetElapsedDeterministicTime() +
670 local_params_.shaving_search_deterministic_time());
672 shaving_proto->set_name(absl::StrCat(
"shaving_var_", state->var_index,
673 (state->minimize ?
"_min" :
"_max")));
676 if (local_params_.cp_model_presolve()) {
677 std::vector<int> postsolve_mapping;
678 CpModelProto mapping_proto;
679 auto context = std::make_unique<PresolveContext>(local_model, shaving_proto,
684 CpSolverResponse tmp_response;
693 if (has_no_overlap_2d) {
694 std::vector<int> postsolve_mapping;
695 CpModelProto mapping_proto;
696 auto context = std::make_unique<PresolveContext>(local_model, shaving_proto,
698 context->InitializeNewDomains();
699 context->UpdateNewConstraintsVariableUsage();
700 const int num_constraints = shaving_proto->constraints().size();
701 std::vector<bool> useful_interval(num_constraints,
false);
702 std::vector<int> no_overalp_2d;
703 for (
int c = 0;
c < num_constraints; ++
c) {
704 const ConstraintProto& ct = shaving_proto->constraints(c);
707 if (context->VarToConstraints(var).size() > 1) {
708 useful_interval[
c] =
true;
713 no_overalp_2d.push_back(c);
716 for (
const int c : no_overalp_2d) {
717 NoOverlap2DConstraintProto* data =
718 shaving_proto->mutable_constraints(c)->mutable_no_overlap_2d();
720 const int num_intervals = data->x_intervals().size();
721 for (
int i = 0;
i < num_intervals; ++
i) {
722 const int x = data->x_intervals(
i);
723 const int y = data->y_intervals(
i);
724 if (!useful_interval[x] && !useful_interval[y])
continue;
725 data->set_x_intervals(new_size, x);
726 data->set_y_intervals(new_size, y);
729 data->mutable_x_intervals()->Truncate(new_size);
730 data->mutable_y_intervals()->Truncate(new_size);
734 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
735 const std::string shaving_name = absl::StrCat(
736 absl::GetFlag(FLAGS_cp_model_dump_prefix),
"shaving_var_",
737 state->var_index, (state->minimize ?
"_min" :
"_max"),
".pb.txt");
738 LOG(INFO) <<
"Dumping shaving model to '" << shaving_name <<
"'.";
742 auto* local_response_manager =
743 local_model->GetOrCreate<SharedResponseManager>();
744 local_response_manager->InitializeObjective(*shaving_proto);
745 local_response_manager->SetSynchronizationMode(
true);
754 if (time_limit->LimitReached())
return false;
bool Connected(int node1, int node2)
bool AddEdge(int node1, int node2)
void SetNumberOfNodes(int num_nodes)
Domain IntersectionWith(const Domain &domain) const
Domain Complement() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::int64_t inner_objective_lower_bound() const
::operations_research::sat::CpSolverStatus status() const
::int64_t solution(int index) const
::int32_t x_intervals(int index) const
ObjectiveShavingSolver(const SatParameters &local_parameters, NeighborhoodGeneratorHelper *helper, SharedClasses *shared)
~ObjectiveShavingSolver() override
void Synchronize() override
bool TaskIsAvailable() override
std::function< void()> GenerateTask(int64_t task_id) override
SubSolver(absl::string_view name, SubsolverType type)
void AddTaskDeterministicDuration(double deterministic_duration)
bool TaskIsAvailable() override
std::function< void()> GenerateTask(int64_t task_id) override
VariablesShavingSolver(const SatParameters &local_parameters, NeighborhoodGeneratorHelper *helper, SharedClasses *shared)
void Synchronize() override
~VariablesShavingSolver() override
void ProcessLocalResponse(const CpSolverResponse &local_response, const State &state)
void SolveLoadedCpModel(const CpModelProto &model_proto, Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int CombineSeed(int base_seed, int64_t delta)
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void QuickSolveWithHint(const CpModelProto &model_proto, Model *model)
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, absl::Span< const Domain > domains, std::function< bool(int)> active_constraints, PresolveContext *context, std::vector< int > *interval_mapping)
void PostsolveResponseWrapper(const SatParameters ¶ms, int num_variable_in_original_model, const CpModelProto &mapping_proto, absl::Span< const int > postsolve_mapping, std::vector< int64_t > *solution)
void LoadCpModel(const CpModelProto &model_proto, Model *model)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
const CpModelProto & model_proto
bool shave_using_objective