24#include "absl/cleanup/cleanup.h"
25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/flags/flag.h"
28#include "absl/log/check.h"
29#include "absl/meta/type_traits.h"
30#include "absl/random/random.h"
31#include "absl/strings/string_view.h"
32#include "absl/types/span.h"
35#if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
38#include "ortools/linear_solver/linear_solver.pb.h"
39#include "ortools/sat/cp_model.pb.h"
50#include "ortools/sat/sat_parameters.pb.h"
59 int, max_hs_strategy, 0,
60 "MaxHsStrategy: 0 extract only objective variable, 1 extract all variables "
61 "colocated with objective variables, 2 extract all variables in the "
68 const CpModelProto& model_proto,
70 const std::function<
void()>& feasible_solution_observer,
Model* model)
71 : model_proto_(model_proto),
72 objective_definition_(objective_definition),
73 feasible_solution_observer_(feasible_solution_observer),
75 sat_solver_(model->GetOrCreate<
SatSolver>()),
76 time_limit_(model->GetOrCreate<
TimeLimit>()),
77 parameters_(*model->GetOrCreate<SatParameters>()),
82 request_.set_solver_specific_parameters(
"limits/gap = 0");
83 request_.set_solver_type(MPModelRequest::SCIP_MIXED_INTEGER_PROGRAMMING);
86bool HittingSetOptimizer::ImportFromOtherWorkers() {
88 for (
const auto& cb : level_zero_callbacks->callbacks) {
100 std::vector<Literal> assumptions,
101 std::vector<std::vector<Literal>>* cores) {
103 const double saved_dlimit = time_limit_->GetDeterministicLimit();
104 auto cleanup = ::absl::MakeCleanup([
this, saved_dlimit]() {
105 time_limit_->ChangeDeterministicLimit(saved_dlimit);
108 bool first_loop =
true;
114 std::shuffle(assumptions.begin(), assumptions.end(), *random_);
119 std::vector<Literal> core = sat_solver_->GetLastIncompatibleDecisions();
120 if (sat_solver_->parameters().core_minimization_level() > 0) {
123 CHECK(!core.empty());
124 cores->push_back(core);
125 if (!parameters_.find_multiple_cores())
break;
129 CHECK(!core.empty());
130 const Literal random_literal =
131 core[absl::Uniform<int>(*random_, 0, core.size())];
132 for (
int i = 0;
i < assumptions.size(); ++
i) {
133 if (assumptions[
i] == random_literal) {
134 std::swap(assumptions[
i], assumptions.back());
135 assumptions.pop_back();
143 time_limit_->ChangeDeterministicLimit(std::min(
144 saved_dlimit, time_limit_->GetElapsedDeterministicTime() + 1.0));
147 }
while (!assumptions.empty());
152int HittingSetOptimizer::GetExtractedIndex(IntegerVariable var)
const {
153 if (var.value() >= sat_var_to_mp_var_.size())
return kUnextracted;
154 return sat_var_to_mp_var_[var];
157void HittingSetOptimizer::ExtractObjectiveVariables() {
158 const std::vector<IntegerVariable>& variables = objective_definition_.vars;
159 const std::vector<IntegerValue>& coefficients = objective_definition_.coeffs;
160 MPModelProto* hs_model = request_.mutable_model();
164 if (obj_constraint_ ==
nullptr) {
165 obj_constraint_ = hs_model->add_constraint();
166 obj_constraint_->set_lower_bound(-std::numeric_limits<double>::infinity());
167 obj_constraint_->set_upper_bound(std::numeric_limits<double>::infinity());
171 for (
int i = 0;
i < variables.size(); ++
i) {
172 IntegerVariable var = variables[
i];
173 IntegerValue coeff = coefficients[
i];
183 normalized_objective_variables_.push_back(var);
184 normalized_objective_coefficients_.push_back(coeff);
186 normalized_objective_variables_.push_back(
NegationOf(var));
187 normalized_objective_coefficients_.push_back(-coeff);
191 const int index = hs_model->variable_size();
192 obj_constraint_->add_var_index(index);
193 obj_constraint_->add_coefficient(
ToDouble(coeff));
195 MPVariableProto* var_proto = hs_model->add_variable();
196 var_proto->set_lower_bound(
ToDouble(integer_trail_->LowerBound(var)));
197 var_proto->set_upper_bound(
ToDouble(integer_trail_->UpperBound(var)));
198 var_proto->set_objective_coefficient(
ToDouble(coeff));
199 var_proto->set_is_integer(
true);
202 const int max_index = std::max(var.value(),
NegationOf(var).value());
203 if (max_index >= sat_var_to_mp_var_.size()) {
204 sat_var_to_mp_var_.resize(max_index + 1, -1);
206 sat_var_to_mp_var_[var] = index;
208 extracted_variables_info_.push_back({var, var_proto});
212void HittingSetOptimizer::ExtractAdditionalVariables(
213 absl::Span<const IntegerVariable> to_extract) {
214 MPModelProto* hs_model = request_.mutable_model();
216 VLOG(2) <<
"Extract " << to_extract.size() <<
" additional variables";
217 for (IntegerVariable tmp_var : to_extract) {
218 if (GetExtractedIndex(tmp_var) != kUnextracted)
continue;
223 const int index = hs_model->variable_size();
224 MPVariableProto* var_proto = hs_model->add_variable();
225 var_proto->set_lower_bound(
ToDouble(integer_trail_->LowerBound(var)));
226 var_proto->set_upper_bound(
ToDouble(integer_trail_->UpperBound(var)));
227 var_proto->set_is_integer(
true);
230 const int max_index = std::max(var.value(),
NegationOf(var).value());
231 if (max_index >= sat_var_to_mp_var_.size()) {
232 sat_var_to_mp_var_.resize(max_index + 1, -1);
234 sat_var_to_mp_var_[var] = index;
236 extracted_variables_info_.push_back({var, var_proto});
247std::vector<IntegerVariable>
248HittingSetOptimizer::ComputeAdditionalVariablesToExtract() {
249 absl::flat_hash_set<IntegerVariable> result_set;
250 if (absl::GetFlag(FLAGS_max_hs_strategy) == 0)
return {};
251 const bool extract_all = absl::GetFlag(FLAGS_max_hs_strategy) == 2;
253 for (
const std::vector<Literal>& literals : relaxation_.at_most_ones) {
254 bool found_at_least_one = extract_all;
255 for (
const Literal literal : literals) {
256 if (GetExtractedIndex(integer_encoder_->GetLiteralView(literal)) !=
258 found_at_least_one =
true;
260 if (found_at_least_one)
break;
262 if (!found_at_least_one)
continue;
263 for (
const Literal literal : literals) {
264 const IntegerVariable var = integer_encoder_->GetLiteralView(literal);
265 if (GetExtractedIndex(var) == kUnextracted) {
271 for (
const LinearConstraint& linear : relaxation_.linear_constraints) {
272 bool found_at_least_one = extract_all;
273 for (
const IntegerVariable var : linear.VarsAsSpan()) {
274 if (GetExtractedIndex(var) != kUnextracted) {
275 found_at_least_one =
true;
277 if (found_at_least_one)
break;
279 if (!found_at_least_one)
continue;
280 for (
const IntegerVariable var : linear.VarsAsSpan()) {
281 if (GetExtractedIndex(var) == kUnextracted) {
287 std::vector<IntegerVariable> result(result_set.begin(), result_set.end());
288 std::sort(result.begin(), result.end());
293void HittingSetOptimizer::ProjectAndAddAtMostOne(
294 absl::Span<const Literal> literals) {
295 LinearConstraintBuilder builder(model_, 0, 1);
296 for (
const Literal& literal : literals) {
297 if (!builder.AddLiteralTerm(literal, 1)) {
298 VLOG(3) <<
"Could not extract literal " << literal;
302 if (ProjectAndAddLinear(builder.Build()) !=
nullptr) {
303 num_extracted_at_most_ones_++;
307MPConstraintProto* HittingSetOptimizer::ProjectAndAddLinear(
309 int num_extracted_variables = 0;
310 for (
int i = 0;
i < linear.num_terms; ++
i) {
312 num_extracted_variables++;
315 if (num_extracted_variables <= 1)
return nullptr;
317 MPConstraintProto* ct = request_.mutable_model()->add_constraint();
318 ProjectLinear(linear, ct);
323 MPConstraintProto* ct) {
324 IntegerValue lb = linear.lb;
325 IntegerValue ub = linear.ub;
327 for (
int i = 0;
i < linear.num_terms; ++
i) {
328 const IntegerVariable var = linear.vars[
i];
329 const IntegerValue coeff = linear.coeffs[
i];
332 if (index != kUnextracted) {
333 ct->add_var_index(index);
336 const IntegerValue var_lb = integer_trail_->LevelZeroLowerBound(var);
337 const IntegerValue var_ub = integer_trail_->LevelZeroUpperBound(var);
353bool HittingSetOptimizer::ComputeInitialMpModel() {
354 if (!ImportFromOtherWorkers())
return false;
356 ExtractObjectiveVariables();
359 ActivityBoundHelper activity_bound_helper;
360 activity_bound_helper.AddAllAtMostOnes(model_proto_);
361 for (
const auto& ct : model_proto_.constraints()) {
363 model_, &relaxation_, &activity_bound_helper);
366 ExtractAdditionalVariables(ComputeAdditionalVariablesToExtract());
369 for (
const auto& literals : relaxation_.at_most_ones) {
370 ProjectAndAddAtMostOne(literals);
372 if (num_extracted_at_most_ones_ > 0) {
373 VLOG(2) <<
"Projected " << num_extracted_at_most_ones_ <<
"/"
374 << relaxation_.at_most_ones.size() <<
" at_most_ones constraints";
377 for (
int i = 0;
i < relaxation_.linear_constraints.size(); ++
i) {
378 MPConstraintProto* ct =
379 ProjectAndAddLinear(relaxation_.linear_constraints[
i]);
380 if (ct !=
nullptr) linear_extract_info_.push_back({
i, ct});
382 if (!linear_extract_info_.empty()) {
383 VLOG(2) <<
"Projected " << linear_extract_info_.size() <<
"/"
384 << relaxation_.linear_constraints.size() <<
" linear constraints";
389void HittingSetOptimizer::TightenMpModel() {
391 for (
const auto& [var, var_proto] : extracted_variables_info_) {
392 var_proto->set_lower_bound(
ToDouble(integer_trail_->LowerBound(var)));
393 var_proto->set_upper_bound(
ToDouble(integer_trail_->UpperBound(var)));
397 for (
const auto& [index, ct] : linear_extract_info_) {
398 const double original_lb = ct->lower_bound();
399 const double original_ub = ct->upper_bound();
401 ProjectLinear(relaxation_.linear_constraints[index], ct);
402 if (original_lb != ct->lower_bound() || original_ub != ct->upper_bound()) {
407 VLOG(2) <<
"Tightened " << tightened <<
" linear constraints";
411bool HittingSetOptimizer::ProcessSolution() {
412 const std::vector<IntegerVariable>& variables = objective_definition_.vars;
413 const std::vector<IntegerValue>& coefficients = objective_definition_.coeffs;
417 IntegerValue objective(0);
418 for (
int i = 0;
i < variables.size(); ++
i) {
420 coefficients[
i] * IntegerValue(model_->Get(
Value(variables[
i])));
423 integer_trail_->UpperBound(objective_definition_.objective_var)) {
427 if (feasible_solution_observer_ !=
nullptr) {
428 feasible_solution_observer_();
433 sat_solver_->Backtrack(0);
434 sat_solver_->SetAssumptionLevel(0);
435 if (!integer_trail_->Enqueue(
444void HittingSetOptimizer::AddCoresToTheMpModel(
445 absl::Span<
const std::vector<Literal>> cores) {
446 MPModelProto* hs_model = request_.mutable_model();
448 for (
const std::vector<Literal>& core : cores) {
450 if (core.size() == 1) {
451 for (
const int index : assumption_to_indices_.at(core.front().Index())) {
452 const IntegerVariable var = normalized_objective_variables_[index];
453 const double new_bound =
ToDouble(integer_trail_->LowerBound(var));
455 hs_model->mutable_variable(index)->set_lower_bound(new_bound);
457 hs_model->mutable_variable(index)->set_upper_bound(-new_bound);
464 MPConstraintProto* at_least_one = hs_model->add_constraint();
465 at_least_one->set_lower_bound(1.0);
466 for (
const Literal lit : core) {
467 for (
const int index : assumption_to_indices_.at(lit.Index())) {
468 const IntegerVariable var = normalized_objective_variables_[index];
469 const double sat_lb =
ToDouble(integer_trail_->LowerBound(var));
475 const double hs_value =
476 std::round(response_.variable_value(index)) * sign;
478 if (hs_value == sat_lb) {
479 at_least_one->add_var_index(index);
480 at_least_one->add_coefficient(sign);
481 at_least_one->set_lower_bound(at_least_one->lower_bound() + hs_value);
485 const std::pair<int, int64_t> key = {index,
486 static_cast<int64_t
>(hs_value)};
487 const int new_bool_var_index = hs_model->variable_size();
488 const auto [it, inserted] =
489 mp_integer_literals_.insert({key, new_bool_var_index});
491 at_least_one->add_var_index(it->second);
492 at_least_one->add_coefficient(1.0);
496 MPVariableProto* bool_var = hs_model->add_variable();
497 bool_var->set_lower_bound(0);
498 bool_var->set_upper_bound(1);
499 bool_var->set_is_integer(
true);
503 MPConstraintProto* implied_bound = hs_model->add_constraint();
504 implied_bound->set_lower_bound(sat_lb);
505 implied_bound->add_var_index(index);
506 implied_bound->add_coefficient(sign);
507 implied_bound->add_var_index(new_bool_var_index);
508 implied_bound->add_coefficient(sat_lb - hs_value - 1.0);
516std::vector<Literal> HittingSetOptimizer::BuildAssumptions(
517 IntegerValue stratified_threshold,
518 IntegerValue* next_stratified_threshold) {
519 std::vector<Literal> assumptions;
522 for (
int i = 0;
i < normalized_objective_variables_.size(); ++
i) {
523 const IntegerVariable var = normalized_objective_variables_[
i];
524 const IntegerValue coeff = normalized_objective_coefficients_[
i];
529 const IntegerValue hs_value(
530 static_cast<int64_t
>(std::round(response_.variable_value(
i))) *
534 if (hs_value == integer_trail_->UpperBound(var))
continue;
537 if (coeff < stratified_threshold) {
538 *next_stratified_threshold = std::max(*next_stratified_threshold, coeff);
542 assumptions.push_back(integer_encoder_->GetOrCreateAssociatedLiteral(
544 assumption_to_indices_[assumptions.back().Index()].push_back(
i);
555#if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
565 for (
int iter = 0;; ++iter) {
572 if (shared_response_ !=
nullptr) {
573 const IntegerValue best_lower_bound =
574 shared_response_->GetInnerObjectiveLowerBound();
575 obj_constraint_->set_lower_bound(
ToDouble(best_lower_bound));
583 if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
594 if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
598 const IntegerValue mip_objective(
599 static_cast<int64_t
>(std::round(response_.objective_value())));
600 VLOG(2) <<
"--" << iter
601 <<
"-- constraints:" << request_.mutable_model()->constraint_size()
602 <<
" variables:" << request_.mutable_model()->variable_size()
603 <<
" hs_lower_bound:"
604 << objective_definition_.ScaleIntegerObjective(mip_objective)
605 <<
" strat:" << stratified_threshold;
611 if (!integer_trail_->Enqueue(
619 sat_solver_->Backtrack(0);
620 sat_solver_->SetAssumptionLevel(0);
621 assumption_to_indices_.clear();
622 IntegerValue next_stratified_threshold(0);
623 const std::vector<Literal> assumptions =
624 BuildAssumptions(stratified_threshold, &next_stratified_threshold);
627 if (assumptions.empty() && next_stratified_threshold > 0) {
628 CHECK_LT(next_stratified_threshold, stratified_threshold);
629 stratified_threshold = next_stratified_threshold;
637 result = FindMultipleCoresForMaxHs(assumptions, &temp_cores_);
640 if (parameters_.stop_after_first_solution()) {
643 if (temp_cores_.empty()) {
646 stratified_threshold = next_stratified_threshold;
647 if (stratified_threshold == 0)
break;
654 if (time_limit_->LimitReached())
break;
659 sat_solver_->Backtrack(0);
660 sat_solver_->SetAssumptionLevel(0);
661 AddCoresToTheMpModel(temp_cores_);
666 LOG(FATAL) <<
"Not supported.";
HittingSetOptimizer(const CpModelProto &model_proto, const ObjectiveDefinition &objective_definition, const std::function< void()> &feasible_solution_observer, Model *model)
SatSolver::Status Optimize()
void NotifyThatModelIsUnsat()
ABSL_FLAG(int, max_hs_strategy, 0, "MaxHsStrategy: 0 extract only objective variable, 1 extract all variables " "colocated with objective variables, 2 extract all variables in the " "linearization")
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
void TryToLinearizeConstraint(const CpModelProto &, const ConstraintProto &ct, int linearization_level, Model *model, LinearRelaxation *relaxation, ActivityBoundHelper *activity_helper)
Adds linearization of different types of constraints.
IntegerVariable PositiveVariable(IntegerVariable i)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
This checks that the variable is fixed.
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
bool VariableIsPositive(IntegerVariable i)
double ToDouble(IntegerValue value)
In SWIG mode, we don't want anything besides these top-level includes.
MPSolutionResponse SolveMPModel(LazyMutableCopy< MPModelRequest > request, const SolveInterrupter *interrupter)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)