23#include "absl/base/thread_annotations.h"
24#include "absl/flags/flag.h"
25#include "absl/log/check.h"
26#include "absl/random/distributions.h"
27#include "absl/strings/str_cat.h"
28#include "absl/synchronization/mutex.h"
52 local_params_(local_parameters),
55 local_proto_(shared->model_proto) {}
58 shared_->stat_tables->AddTimingStat(*
this);
62 if (shared_->SearchIsDone())
return false;
65 absl::MutexLock mutex_lock(&mutex_);
66 return !task_in_flight_;
71 absl::MutexLock mutex_lock(&mutex_);
72 stop_current_chunk_.store(
false);
73 task_in_flight_ =
true;
74 objective_lb_ = shared_->response->GetInnerObjectiveLowerBound();
75 objective_ub_ = shared_->response->GetInnerObjectiveUpperBound();
77 return [
this, task_id]() {
78 if (ResetAndSolveModel(task_id)) {
79 const CpSolverResponse local_response =
82 if (local_response.status() == CpSolverStatus::OPTIMAL ||
83 local_response.status() == CpSolverStatus::FEASIBLE) {
84 std::vector<int64_t> solution_values(local_response.solution().begin(),
85 local_response.solution().end());
86 if (local_params_.cp_model_presolve()) {
87 const int num_original_vars = shared_->model_proto.variables_size();
89 mapping_proto_, postsolve_mapping_,
92 shared_->response->NewSolution(solution_values, Info());
93 }
else if (local_response.status() == CpSolverStatus::INFEASIBLE) {
94 absl::MutexLock mutex_lock(&mutex_);
95 shared_->response->UpdateInnerObjectiveBounds(
96 Info(), current_objective_target_ub_ + 1, objective_ub_);
100 absl::MutexLock mutex_lock(&mutex_);
101 task_in_flight_ =
false;
102 if (local_sat_model_ !=
nullptr) {
103 const double dtime = local_sat_model_->GetOrCreate<
TimeLimit>()
104 ->GetElapsedDeterministicTime();
106 shared_->time_limit->AdvanceDeterministicTime(dtime);
112 absl::MutexLock mutex_lock(&mutex_);
113 if (!task_in_flight_)
return;
117 if (stop_current_chunk_)
return;
121 if (shared_->SearchIsDone()) {
122 stop_current_chunk_.store(
true);
126 if (shared_->response->GetInnerObjectiveLowerBound() > objective_lb_) {
127 stop_current_chunk_.store(
true);
132 if (shared_->response->GetInnerObjectiveUpperBound() <=
133 current_objective_target_ub_ &&
134 current_objective_target_ub_ != objective_lb_) {
135 stop_current_chunk_.store(
true);
140 if (current_objective_target_ub_ != objective_lb_ &&
141 shared_->response->GetInnerObjectiveUpperBound() -
142 shared_->response->GetInnerObjectiveLowerBound() <=
143 local_params_.shaving_search_threshold()) {
144 stop_current_chunk_.store(
true);
148std::string ObjectiveShavingSolver::Info() {
149 return absl::StrCat(
name(),
" (vars=", local_proto_.variables().size(),
150 " csts=", local_proto_.constraints().size(),
")");
153bool ObjectiveShavingSolver::ResetAndSolveModel(int64_t task_id) {
154 local_sat_model_ = std::make_unique<Model>(
name());
155 *local_sat_model_->GetOrCreate<SatParameters>() = local_params_;
156 local_sat_model_->GetOrCreate<SatParameters>()->set_random_seed(
157 CombineSeed(local_params_.random_seed(), task_id));
161 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
163 auto* random = local_sat_model_->GetOrCreate<ModelRandomGenerator>();
167 *local_proto_.mutable_variables() =
171 IntegerValue objective_lb;
172 IntegerValue chosen_objective_ub;
174 absl::MutexLock mutex_lock(&mutex_);
175 objective_lb = objective_lb_;
176 if (objective_ub_ - objective_lb <=
177 local_params_.shaving_search_threshold()) {
178 current_objective_target_ub_ = objective_lb;
180 const IntegerValue mid = (objective_ub_ - objective_lb) / 2;
181 current_objective_target_ub_ =
182 objective_lb + absl::LogUniform<int64_t>(*random, 0, mid.value());
184 chosen_objective_ub = current_objective_target_ub_;
185 VLOG(2) <<
name() <<
": from [" << objective_lb.value() <<
".."
186 << objective_ub_.value() <<
"] <= " << chosen_objective_ub.value();
192 Domain obj_domain = Domain(objective_lb.value(), chosen_objective_ub.value());
193 if (local_proto_.objective().domain_size() > 1) {
196 obj_domain.IntersectionWith(Domain(local_proto_.objective().domain(0),
197 local_proto_.objective().domain(1)));
199 if (local_proto_.objective().vars().size() == 1 &&
200 local_proto_.objective().coeffs(0) == 1) {
202 local_proto_.mutable_variables(local_proto_.objective().vars(0));
203 const Domain reduced_var_domain = obj_domain.IntersectionWith(
204 Domain(obj_var->domain(0), obj_var->domain(1)));
205 if (reduced_var_domain.IsEmpty()) {
210 auto* obj = local_proto_.add_constraints()->mutable_linear();
211 *obj->mutable_vars() = local_proto_.objective().vars();
212 *obj->mutable_coeffs() = local_proto_.objective().coeffs();
213 if (obj_domain.IsEmpty()) {
220 local_proto_.clear_objective();
221 local_proto_.set_name(
222 absl::StrCat(local_proto_.name(),
"_obj_shaving_", objective_lb.value()));
225 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
226 const std::string
name =
227 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
228 "objective_shaving_", objective_lb.value(),
".pb.txt");
229 LOG(INFO) <<
"Dumping objective shaving model to '" <<
name <<
"'.";
234 if (local_params_.cp_model_presolve()) {
235 mapping_proto_.Clear();
236 postsolve_mapping_.clear();
237 auto context = std::make_unique<PresolveContext>(
238 local_sat_model_.get(), &local_proto_, &mapping_proto_);
239 const CpSolverStatus presolve_status =
241 if (presolve_status == CpSolverStatus::INFEASIBLE) {
242 absl::MutexLock mutex_lock(&mutex_);
243 shared_->response->UpdateInnerObjectiveBounds(
263 const SatParameters& local_parameters,
SharedClasses* shared)
265 local_params_(local_parameters),
267 stop_current_chunk_(false),
268 model_proto_(shared->model_proto) {
269 if (shared_->bounds !=
nullptr) {
270 shared_bounds_id_ = shared_->bounds->RegisterNewId();
273 absl::MutexLock mutex_lock(&mutex_);
274 for (
const IntegerVariableProto& var_proto : model_proto_.variables()) {
275 var_domains_.push_back(ReadDomainFromProto(var_proto));
280 if (!VLOG_IS_ON(1))
return;
281 if (shared_ ==
nullptr || shared_->stats ==
nullptr)
return;
282 std::vector<std::pair<std::string, int64_t>> stats;
283 absl::MutexLock mutex_lock(&mutex_);
284 stats.push_back({
"variable_shaving/num_vars_tried", num_vars_tried_});
285 stats.push_back({
"variable_shaving/num_vars_shaved", num_vars_shaved_});
287 {
"variable_shaving/num_infeasible_found", num_infeasible_found_});
288 shared_->stats->AddStats(stats);
292 return !shared_->SearchIsDone();
296 const CpSolverResponse& local_response,
const State& state) {
297 if (local_response.status() != CpSolverStatus::INFEASIBLE)
return;
299 absl::MutexLock lock(&mutex_);
301 Domain new_domain = domain;
302 ++num_infeasible_found_;
304 VLOG(1) <<
name() <<
": var(" << state.
var_index <<
") " << domain <<
" ==> "
307 if (domain != new_domain) {
309 if (shared_->bounds !=
nullptr && !new_domain.
IsEmpty()) {
310 shared_->bounds->ReportPotentialNewBounds(
313 var_domains_[state.
var_index] = new_domain;
314 if (var_domains_[state.
var_index].IsEmpty()) {
315 shared_->response->NotifyThatImprovingProblemIsInfeasible(
316 "Unsat during variables shaving");
323 return [
this, task_id]()
mutable {
324 Model local_sat_model;
325 CpModelProto shaving_proto;
327 if (ResetAndSolveModel(task_id, &state, &local_sat_model, &shaving_proto)) {
328 const CpSolverResponse local_response =
333 absl::MutexLock mutex_lock(&mutex_);
337 shared_->time_limit->AdvanceDeterministicTime(dtime);
342 absl::MutexLock mutex_lock(&mutex_);
345 if (stop_current_chunk_)
return;
347 if (shared_->SearchIsDone()) {
348 stop_current_chunk_.store(
true);
351 if (shared_->bounds !=
nullptr) {
352 std::vector<int> model_variables;
353 std::vector<int64_t> new_lower_bounds;
354 std::vector<int64_t> new_upper_bounds;
355 shared_->bounds->GetChangedBounds(shared_bounds_id_, &model_variables,
356 &new_lower_bounds, &new_upper_bounds);
358 for (
int i = 0;
i < model_variables.size(); ++
i) {
359 const int var = model_variables[
i];
360 const int64_t new_lb = new_lower_bounds[
i];
361 const int64_t new_ub = new_upper_bounds[
i];
362 const Domain& old_domain = var_domains_[var];
366 shared_->response->NotifyThatImprovingProblemIsInfeasible(
367 "Unsat during variables shaving");
370 var_domains_[var] = new_domain;
375std::string VariablesShavingSolver::Info() {
376 return absl::StrCat(
name(),
" (vars=", model_proto_.variables().size(),
377 " csts=", model_proto_.constraints().size(),
")");
380int64_t VariablesShavingSolver::DomainSize(
int var)
const
381 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
382 return var_domains_[var].Size();
385bool VariablesShavingSolver::VarIsFixed(
int int_var)
const
386 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
387 return var_domains_[int_var].IsFixed();
390bool VariablesShavingSolver::ConstraintIsInactive(
int c)
const
391 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
392 for (
const int ref : model_proto_.constraints(c).enforcement_literal()) {
394 if (VarIsFixed(var) && var_domains_[var].Min() == (var == ref ? 0 : 1)) {
401bool VariablesShavingSolver::FindNextVar(State* state)
402 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
403 const int num_vars = var_domains_.size();
404 const int max_index = 2 * num_vars;
405 for (
int i = 0;
i < 2 * num_vars; ++
i) {
406 if (++current_index_ == max_index) current_index_ = 0;
407 const int var = current_index_ / 2;
408 if (VarIsFixed(var))
continue;
411 if (model_proto_.has_objective() &&
412 model_proto_.objective().vars_size() == 1 &&
413 var == model_proto_.objective().vars(0)) {
417 state->var_index = var;
418 state->minimize = current_index_ % 2 == 0;
424void VariablesShavingSolver::CopyModelConnectedToVar(
425 State* state,
Model* local_sat_model, CpModelProto* shaving_proto)
426 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
427 auto var_to_node = [](
int var) {
return var; };
428 auto ct_to_node = [
this](
int ct) {
429 return ct + model_proto_.variables_size();
433 DenseConnectedComponentsFinder cc_finder;
435 model_proto_.variables_size());
436 for (
int i = 0;
i < model_proto_.constraints_size(); ++
i) {
437 if (ConstraintIsInactive(
i))
continue;
438 const ConstraintProto& ct = model_proto_.constraints(
i);
439 const int ct_node = ct_to_node(
i);
441 if (VarIsFixed(var))
continue;
442 cc_finder.
AddEdge(ct_node, var_to_node(var));
445 cc_finder.
AddEdge(ct_node, ct_to_node(interval));
449 DCHECK(shaving_proto->variables().empty());
450 DCHECK(shaving_proto->constraints().empty());
451 const int root_index = var_to_node(state->var_index);
453 const auto active_constraints = [&cc_finder, root_index,
454 &ct_to_node](
int ct) {
455 return cc_finder.
Connected(root_index, ct_to_node(ct));
460 model_proto_, var_domains_, active_constraints, &context);
463 shaving_proto->constraints_size() < model_proto_.constraints_size()) {
464 int num_active_variables = 0;
465 for (
int i = 0;
i < var_domains_.size(); ++
i) {
466 if (cc_finder.
Connected(root_index, var_to_node(
i))) {
467 ++num_active_variables;
471 LOG(INFO) <<
"#constraints:" << shaving_proto->constraints_size() <<
"/"
472 << model_proto_.constraints_size()
473 <<
" #variables:" << num_active_variables <<
"/"
474 << var_domains_.size();
477 const Domain domain =
479 shaving_proto->clear_objective();
482 if (domain.Size() > local_params_.shaving_search_threshold()) {
483 const int64_t mid_range = (domain.
Max() - domain.
Min()) / 2;
485 delta = absl::LogUniform<int64_t>(*random, 0, mid_range);
488 if (state->minimize) {
489 state->reduced_domain =
490 domain.IntersectionWith({domain.
Min(), domain.
Min() + delta});
492 state->reduced_domain =
493 domain.IntersectionWith({domain.
Max() - delta, domain.
Max()});
497 shaving_proto->mutable_variables(state->var_index));
499 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
500 const std::string shaving_name = absl::StrCat(
501 absl::GetFlag(FLAGS_cp_model_dump_prefix),
"shaving_var_",
502 state->var_index, (state->minimize ?
"_min" :
"_max"),
".pb.txt");
503 LOG(INFO) <<
"Dumping shaving model to '" << shaving_name <<
"'.";
508bool VariablesShavingSolver::ResetAndSolveModel(int64_t task_id, State* state,
509 Model* local_sat_model,
510 CpModelProto* shaving_proto) {
511 *local_sat_model->GetOrCreate<SatParameters>() = local_params_;
512 local_sat_model->GetOrCreate<SatParameters>()->set_random_seed(
513 CombineSeed(local_params_.random_seed(), task_id));
516 absl::MutexLock lock(&mutex_);
517 if (!FindNextVar(state))
return false;
518 CopyModelConnectedToVar(state, local_sat_model, shaving_proto);
522 auto*
time_limit = local_sat_model->GetOrCreate<TimeLimit>();
523 shared_->time_limit->UpdateLocalLimit(
time_limit);
524 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
527 local_params_.shaving_search_deterministic_time());
529 shaving_proto->set_name(absl::StrCat(
"shaving_var_", state->var_index,
530 (state->minimize ?
"_min" :
"_max")));
533 if (local_params_.cp_model_presolve()) {
534 std::vector<int> postsolve_mapping;
535 CpModelProto mapping_proto;
536 auto context = std::make_unique<PresolveContext>(
537 local_sat_model, shaving_proto, &mapping_proto);
538 const CpSolverStatus presolve_status =
540 if (presolve_status == CpSolverStatus::INFEASIBLE) {
541 CpSolverResponse tmp_response;
542 tmp_response.set_status(CpSolverStatus::INFEASIBLE);
548 auto* local_response_manager =
549 local_sat_model->GetOrCreate<SharedResponseManager>();
550 local_response_manager->InitializeObjective(*shaving_proto);
551 local_response_manager->SetSynchronizationMode(
true);
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
void UpdateLocalLimit(TimeLimit *local_limit)
Neighborhood FullNeighborhood() const
Returns a neighborhood that correspond to the full problem.
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)
std::string name() const
Returns the name of this SubSolver. Used in logs.
VariablesShavingSolver(const SatParameters &local_parameters, SharedClasses *shared)
bool TaskIsAvailable() override
std::function< void()> GenerateTask(int64_t task_id) override
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)
We assume delta >= 0 and we only use the low bit of delta.
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, absl::Span< const Domain > domains, std::function< bool(int)> active_constraints, PresolveContext *context)
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
Convenient wrapper to call the full presolve.
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
Returns the sorted list of interval used by a constraint.
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.
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)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t Max() const
Returns the max of the domain.
int64_t Min() const
Returns the min of the domain.
const CpModelProto & model_proto
These are never nullptr.
ModelSharedTimeLimit *const time_limit