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"
51 local_params_(local_parameters),
54 local_proto_(shared->model_proto) {}
64 absl::MutexLock mutex_lock(&mutex_);
65 return !task_in_flight_;
70 absl::MutexLock mutex_lock(&mutex_);
71 stop_current_chunk_.store(
false);
72 task_in_flight_ =
true;
76 return [
this, task_id]() {
77 if (ResetModel(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_,
93 }
else if (local_response.status() == CpSolverStatus::INFEASIBLE) {
94 absl::MutexLock mutex_lock(&mutex_);
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();
112 absl::MutexLock mutex_lock(&mutex_);
113 if (!task_in_flight_)
return;
117 if (stop_current_chunk_)
return;
122 stop_current_chunk_.store(
true);
127 stop_current_chunk_.store(
true);
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_ &&
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::ResetModel(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)));
207 auto* obj = local_proto_.add_constraints()->mutable_linear();
208 *obj->mutable_vars() = local_proto_.objective().vars();
209 *obj->mutable_coeffs() = local_proto_.objective().coeffs();
214 local_proto_.clear_objective();
217 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
218 const std::string
name =
219 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
220 "objective_shaving_", objective_lb.value(),
".pb.txt");
221 LOG(INFO) <<
"Dumping objective shaving model to '" <<
name <<
"'.";
226 if (local_params_.cp_model_presolve()) {
227 mapping_proto_.Clear();
228 postsolve_mapping_.clear();
229 auto context = std::make_unique<PresolveContext>(
230 local_sat_model_.get(), &local_proto_, &mapping_proto_);
231 const CpSolverStatus presolve_status =
233 if (presolve_status == CpSolverStatus::INFEASIBLE) {
234 absl::MutexLock mutex_lock(&mutex_);
255 const SatParameters& local_parameters,
SharedClasses* shared)
257 local_params_(local_parameters),
259 stop_current_chunk_(false),
260 model_proto_(shared->model_proto) {
261 if (shared_->
bounds !=
nullptr) {
262 shared_bounds_id_ = shared_->bounds->RegisterNewId();
265 absl::MutexLock mutex_lock(&mutex_);
266 for (
const IntegerVariableProto& var_proto : model_proto_.variables()) {
267 var_domains_.push_back(ReadDomainFromProto(var_proto));
272 if (!VLOG_IS_ON(1))
return;
273 if (shared_ ==
nullptr || shared_->
stats ==
nullptr)
return;
274 std::vector<std::pair<std::string, int64_t>> stats;
275 absl::MutexLock mutex_lock(&mutex_);
276 stats.push_back({
"variable_shaving/num_vars_tried", num_vars_tried_});
277 stats.push_back({
"variable_shaving/num_vars_shaved", num_vars_shaved_});
279 {
"variable_shaving/num_infeasible_found", num_infeasible_found_});
288 const CpSolverResponse& local_response,
const State& state) {
289 if (local_response.status() != CpSolverStatus::INFEASIBLE)
return;
291 absl::MutexLock lock(&mutex_);
293 Domain new_domain = domain;
294 ++num_infeasible_found_;
296 VLOG(1) <<
name() <<
": var(" << state.
var_index <<
") " << domain <<
" ==> "
299 if (domain != new_domain) {
302 shared_->
bounds->ReportPotentialNewBounds(
305 var_domains_[state.
var_index] = new_domain;
306 if (var_domains_[state.
var_index].IsEmpty()) {
308 "Unsat during variables shaving");
315 return [
this, task_id]()
mutable {
316 Model local_sat_model;
317 CpModelProto shaving_proto;
319 if (ResetModel(task_id, &state, &local_sat_model, &shaving_proto)) {
321 const CpSolverResponse local_response =
326 absl::MutexLock mutex_lock(&mutex_);
335 absl::MutexLock mutex_lock(&mutex_);
338 if (stop_current_chunk_)
return;
341 stop_current_chunk_.store(
true);
344 if (shared_->
bounds !=
nullptr) {
345 std::vector<int> model_variables;
346 std::vector<int64_t> new_lower_bounds;
347 std::vector<int64_t> new_upper_bounds;
348 shared_->
bounds->GetChangedBounds(shared_bounds_id_, &model_variables,
349 &new_lower_bounds, &new_upper_bounds);
351 for (
int i = 0;
i < model_variables.size(); ++
i) {
352 const int var = model_variables[
i];
353 const int64_t new_lb = new_lower_bounds[
i];
354 const int64_t new_ub = new_upper_bounds[
i];
355 const Domain& old_domain = var_domains_[
var];
360 "Unsat during variables shaving");
363 var_domains_[
var] = new_domain;
368std::string VariablesShavingSolver::Info() {
369 return absl::StrCat(
name(),
" (vars=", model_proto_.variables().size(),
370 " csts=", model_proto_.constraints().size(),
")");
373int64_t VariablesShavingSolver::DomainSize(
int var)
const
374 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
375 return var_domains_[
var].Size();
378bool VariablesShavingSolver::VarIsFixed(
int int_var)
const
379 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
380 return var_domains_[int_var].IsFixed();
383bool VariablesShavingSolver::ConstraintIsInactive(
int c)
const
384 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
385 for (
const int ref : model_proto_.constraints(c).enforcement_literal()) {
387 if (VarIsFixed(
var) && var_domains_[
var].Min() == (
var == ref ? 0 : 1)) {
394bool VariablesShavingSolver::FindNextVar(State* state)
395 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
396 const int num_vars = var_domains_.size();
397 const int max_index = 2 * num_vars;
398 for (
int i = 0;
i < 2 * num_vars; ++
i) {
399 if (++current_index_ == max_index) current_index_ = 0;
400 const int var = current_index_ / 2;
401 if (VarIsFixed(
var))
continue;
404 if (model_proto_.has_objective() &&
405 model_proto_.objective().vars_size() == 1 &&
406 var == model_proto_.objective().vars(0)) {
410 state->var_index =
var;
411 state->minimize = current_index_ % 2 == 0;
417void VariablesShavingSolver::CopyModelConnectedToVar(
418 State* state, Model* local_sat_model, CpModelProto* shaving_proto)
419 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
420 auto var_to_node = [](
int var) {
return var; };
421 auto ct_to_node = [
this](
int ct) {
422 return ct + model_proto_.variables_size();
428 model_proto_.variables_size());
429 for (
int i = 0;
i < model_proto_.constraints_size(); ++
i) {
430 if (ConstraintIsInactive(
i))
continue;
431 const ConstraintProto&
ct = model_proto_.constraints(
i);
432 const int ct_node = ct_to_node(
i);
434 if (VarIsFixed(
var))
continue;
442 DCHECK(shaving_proto->variables().empty());
443 DCHECK(shaving_proto->constraints().empty());
444 const int root_index = var_to_node(state->var_index);
446 const auto active_constraints = [&cc_finder, root_index,
447 &ct_to_node](
int ct) {
448 return cc_finder.
Connected(root_index, ct_to_node(
ct));
451 PresolveContext
context(local_sat_model, shaving_proto,
nullptr);
453 model_proto_, var_domains_, active_constraints, &
context);
456 shaving_proto->constraints_size() < model_proto_.constraints_size()) {
457 int num_active_variables = 0;
458 for (
int i = 0;
i < var_domains_.size(); ++
i) {
459 if (cc_finder.
Connected(root_index, var_to_node(
i))) {
460 ++num_active_variables;
464 LOG(INFO) <<
"#constraints:" << shaving_proto->constraints_size() <<
"/"
465 << model_proto_.constraints_size()
466 <<
" #variables:" << num_active_variables <<
"/"
467 << var_domains_.size();
470 const Domain domain =
472 shaving_proto->clear_objective();
475 if (domain.Size() > local_params_.shaving_search_threshold()) {
476 const int64_t mid_range = (domain.Max() - domain.Min()) / 2;
477 auto* random = local_sat_model->GetOrCreate<ModelRandomGenerator>();
478 delta = absl::LogUniform<int64_t>(*random, 0, mid_range);
481 if (state->minimize) {
482 state->reduced_domain =
483 domain.IntersectionWith({domain.Min(), domain.Min() +
delta});
485 state->reduced_domain =
486 domain.IntersectionWith({domain.Max() -
delta, domain.Max()});
490 shaving_proto->mutable_variables(state->var_index));
492 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
493 const std::string shaving_name = absl::StrCat(
494 absl::GetFlag(FLAGS_cp_model_dump_prefix),
"shaving_var_",
495 state->var_index, (state->minimize ?
"_min" :
"_max"),
".pb.txt");
496 LOG(INFO) <<
"Dumping shaving model to '" << shaving_name <<
"'.";
501bool VariablesShavingSolver::ResetModel(int64_t task_id, State* state,
502 Model* local_sat_model,
503 CpModelProto* shaving_proto) {
504 *local_sat_model->GetOrCreate<SatParameters>() = local_params_;
505 local_sat_model->GetOrCreate<SatParameters>()->set_random_seed(
506 CombineSeed(local_params_.random_seed(), task_id));
509 absl::MutexLock lock(&mutex_);
510 if (!FindNextVar(state))
return false;
511 CopyModelConnectedToVar(state, local_sat_model, shaving_proto);
515 auto*
time_limit = local_sat_model->GetOrCreate<TimeLimit>();
517 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
520 local_params_.shaving_search_deterministic_time());
523 if (local_params_.cp_model_presolve()) {
524 std::vector<int> postsolve_mapping;
525 CpModelProto mapping_proto;
526 auto context = std::make_unique<PresolveContext>(
527 local_sat_model, shaving_proto, &mapping_proto);
528 const CpSolverStatus presolve_status =
530 if (presolve_status == CpSolverStatus::INFEASIBLE) {
531 CpSolverResponse tmp_response;
532 tmp_response.set_status(CpSolverStatus::INFEASIBLE);
538 auto* local_response_manager =
539 local_sat_model->GetOrCreate<SharedResponseManager>();
540 local_response_manager->InitializeObjective(*shaving_proto);
541 local_response_manager->SetSynchronizationMode(
true);
A connected components finder that only works on dense ints.
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)
void AdvanceDeterministicTime(double deterministic_duration)
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
IntegerValue GetInnerObjectiveLowerBound()
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
IntegerValue GetInnerObjectiveUpperBound()
void NewSolution(absl::Span< const int64_t > solution_values, const std::string &solution_info, Model *model=nullptr)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
Updates the inner objective bounds.
void AddTimingStat(const SubSolver &subsolver)
Add a line to the corresponding table.
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
Adds a bunch of stats, adding count for the same key together.
void 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)
const std::string name
A name for logging purposes.
GurobiMPCallbackContext * context
void SolveLoadedCpModel(const CpModelProto &model_proto, Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, const std::vector< Domain > &domains, std::function< bool(int)> active_constraints, PresolveContext *context)
int CombineSeed(int base_seed, int64_t delta)
We assume delta >= 0 and we only use the low bit of delta.
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
void PostsolveResponseWrapper(const SatParameters ¶ms, int num_variable_in_original_model, const CpModelProto &mapping_proto, const std::vector< int > &postsolve_mapping, std::vector< int64_t > *solution)
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 LoadCpModel(const CpModelProto &model_proto, Model *model)
In SWIG mode, we don't want anything besides these top-level includes.
const CpModelProto & model_proto
These are never nullptr.
ModelSharedTimeLimit *const time_limit
SharedResponseManager *const response
SharedStatistics *const stats
std::unique_ptr< SharedBoundsManager > bounds
These can be nullptr depending on the options.
SharedStatTables stat_tables
For displaying summary at the end.