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) 
   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 " 
   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>()),
 
   82  request_.set_solver_specific_parameters(
"limits/gap = 0");
 
 
   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_++;
 
  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);
 
  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));
 
  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.";
 
 
static constexpr SolverType SCIP_MIXED_INTEGER_PROGRAMMING
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)