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"
36#if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
39#include "ortools/linear_solver/linear_solver.pb.h"
40#include "ortools/sat/cp_model.pb.h"
49#include "ortools/sat/sat_parameters.pb.h"
58 int, max_hs_strategy, 0,
59 "MaxHsStrategy: 0 extract only objective variable, 1 extract all variables "
60 "colocated with objective variables, 2 extract all variables in the "
67 const CpModelProto& model_proto,
69 const std::function<
void()>& feasible_solution_observer,
Model*
model)
70 : model_proto_(model_proto),
71 objective_definition_(objective_definition),
72 feasible_solution_observer_(feasible_solution_observer),
76 parameters_(*
model->GetOrCreate<SatParameters>()),
81 request_.set_solver_specific_parameters(
"limits/gap = 0");
82 request_.set_solver_type(MPModelRequest::SCIP_MIXED_INTEGER_PROGRAMMING);
85bool HittingSetOptimizer::ImportFromOtherWorkers() {
87 for (
const auto& cb : level_zero_callbacks->callbacks) {
99 std::vector<Literal> assumptions,
100 std::vector<std::vector<Literal>>* cores) {
103 auto cleanup = ::absl::MakeCleanup([
this, saved_dlimit]() {
107 bool first_loop =
true;
113 std::shuffle(assumptions.begin(), assumptions.end(), *random_);
119 if (sat_solver_->
parameters().core_minimization_level() > 0) {
122 CHECK(!core.empty());
123 cores->push_back(core);
124 if (!parameters_.find_multiple_cores())
break;
128 CHECK(!core.empty());
129 const Literal random_literal =
130 core[absl::Uniform<int>(*random_, 0, core.size())];
131 for (
int i = 0;
i < assumptions.size(); ++
i) {
132 if (assumptions[
i] == random_literal) {
133 std::swap(assumptions[
i], assumptions.back());
134 assumptions.pop_back();
146 }
while (!assumptions.empty());
151int HittingSetOptimizer::GetExtractedIndex(IntegerVariable
var)
const {
152 if (
var.value() >= sat_var_to_mp_var_.size())
return kUnextracted;
153 return sat_var_to_mp_var_[
var];
156void HittingSetOptimizer::ExtractObjectiveVariables() {
157 const std::vector<IntegerVariable>& variables = objective_definition_.
vars;
159 MPModelProto* hs_model = request_.mutable_model();
163 if (obj_constraint_ ==
nullptr) {
164 obj_constraint_ = hs_model->add_constraint();
165 obj_constraint_->set_lower_bound(-std::numeric_limits<double>::infinity());
166 obj_constraint_->set_upper_bound(std::numeric_limits<double>::infinity());
170 for (
int i = 0;
i < variables.size(); ++
i) {
171 IntegerVariable
var = variables[
i];
182 normalized_objective_variables_.push_back(
var);
183 normalized_objective_coefficients_.push_back(coeff);
186 normalized_objective_coefficients_.push_back(-coeff);
190 const int index = hs_model->variable_size();
191 obj_constraint_->add_var_index(
index);
192 obj_constraint_->add_coefficient(
ToDouble(coeff));
194 MPVariableProto* var_proto = hs_model->add_variable();
197 var_proto->set_objective_coefficient(
ToDouble(coeff));
198 var_proto->set_is_integer(
true);
202 if (max_index >= sat_var_to_mp_var_.size()) {
203 sat_var_to_mp_var_.
resize(max_index + 1, -1);
207 extracted_variables_info_.push_back({
var, var_proto});
211void HittingSetOptimizer::ExtractAdditionalVariables(
212 absl::Span<const IntegerVariable> to_extract) {
213 MPModelProto* hs_model = request_.mutable_model();
215 VLOG(2) <<
"Extract " << to_extract.size() <<
" additional variables";
216 for (IntegerVariable tmp_var : to_extract) {
217 if (GetExtractedIndex(tmp_var) != kUnextracted)
continue;
222 const int index = hs_model->variable_size();
223 MPVariableProto* var_proto = hs_model->add_variable();
226 var_proto->set_is_integer(
true);
230 if (max_index >= sat_var_to_mp_var_.size()) {
231 sat_var_to_mp_var_.
resize(max_index + 1, -1);
235 extracted_variables_info_.push_back({
var, var_proto});
246std::vector<IntegerVariable>
247HittingSetOptimizer::ComputeAdditionalVariablesToExtract() {
248 absl::flat_hash_set<IntegerVariable> result_set;
249 if (absl::GetFlag(FLAGS_max_hs_strategy) == 0)
return {};
250 const bool extract_all = absl::GetFlag(FLAGS_max_hs_strategy) == 2;
252 for (
const std::vector<Literal>& literals : relaxation_.
at_most_ones) {
253 bool found_at_least_one = extract_all;
254 for (
const Literal
literal : literals) {
257 found_at_least_one =
true;
259 if (found_at_least_one)
break;
261 if (!found_at_least_one)
continue;
262 for (
const Literal
literal : literals) {
264 if (GetExtractedIndex(
var) == kUnextracted) {
271 bool found_at_least_one = extract_all;
272 for (
const IntegerVariable
var : linear.VarsAsSpan()) {
273 if (GetExtractedIndex(
var) != kUnextracted) {
274 found_at_least_one =
true;
276 if (found_at_least_one)
break;
278 if (!found_at_least_one)
continue;
279 for (
const IntegerVariable
var : linear.VarsAsSpan()) {
280 if (GetExtractedIndex(
var) == kUnextracted) {
286 std::vector<IntegerVariable> result(result_set.begin(), result_set.end());
287 std::sort(result.begin(), result.end());
292void HittingSetOptimizer::ProjectAndAddAtMostOne(
293 absl::Span<const Literal> literals) {
294 LinearConstraintBuilder builder(model_, 0, 1);
295 for (
const Literal&
literal : literals) {
296 if (!builder.AddLiteralTerm(
literal, 1)) {
297 VLOG(3) <<
"Could not extract literal " <<
literal;
301 if (ProjectAndAddLinear(builder.Build()) !=
nullptr) {
302 num_extracted_at_most_ones_++;
306MPConstraintProto* HittingSetOptimizer::ProjectAndAddLinear(
307 const LinearConstraint& linear) {
308 int num_extracted_variables = 0;
309 for (
int i = 0;
i < linear.num_terms; ++
i) {
311 num_extracted_variables++;
314 if (num_extracted_variables <= 1)
return nullptr;
316 MPConstraintProto*
ct = request_.mutable_model()->add_constraint();
317 ProjectLinear(linear,
ct);
321void HittingSetOptimizer::ProjectLinear(
const LinearConstraint& linear,
322 MPConstraintProto*
ct) {
323 IntegerValue lb = linear.lb;
324 IntegerValue ub = linear.ub;
326 for (
int i = 0;
i < linear.num_terms; ++
i) {
327 const IntegerVariable
var = linear.vars[
i];
328 const IntegerValue coeff = linear.coeffs[
i];
331 if (
index != kUnextracted) {
352bool HittingSetOptimizer::ComputeInitialMpModel() {
353 if (!ImportFromOtherWorkers())
return false;
355 ExtractObjectiveVariables();
358 ActivityBoundHelper activity_bound_helper;
359 activity_bound_helper.AddAllAtMostOnes(model_proto_);
360 for (
const auto&
ct : model_proto_.constraints()) {
362 model_, &relaxation_, &activity_bound_helper);
365 ExtractAdditionalVariables(ComputeAdditionalVariablesToExtract());
369 ProjectAndAddAtMostOne(literals);
371 if (num_extracted_at_most_ones_ > 0) {
372 VLOG(2) <<
"Projected " << num_extracted_at_most_ones_ <<
"/"
373 << relaxation_.
at_most_ones.size() <<
" at_most_ones constraints";
377 MPConstraintProto*
ct =
379 if (
ct !=
nullptr) linear_extract_info_.push_back({
i,
ct});
381 if (!linear_extract_info_.empty()) {
382 VLOG(2) <<
"Projected " << linear_extract_info_.size() <<
"/"
388void HittingSetOptimizer::TightenMpModel() {
390 for (
const auto& [
var, var_proto] : extracted_variables_info_) {
396 for (
const auto& [
index,
ct] : linear_extract_info_) {
397 const double original_lb =
ct->lower_bound();
398 const double original_ub =
ct->upper_bound();
401 if (original_lb !=
ct->lower_bound() || original_ub !=
ct->upper_bound()) {
406 VLOG(2) <<
"Tightened " << tightened <<
" linear constraints";
410bool HittingSetOptimizer::ProcessSolution() {
411 const std::vector<IntegerVariable>& variables = objective_definition_.
vars;
416 IntegerValue objective(0);
417 for (
int i = 0;
i < variables.size(); ++
i) {
426 if (feasible_solution_observer_ !=
nullptr) {
427 feasible_solution_observer_();
443void HittingSetOptimizer::AddCoresToTheMpModel(
444 absl::Span<
const std::vector<Literal>> cores) {
445 MPModelProto* hs_model = request_.mutable_model();
447 for (
const std::vector<Literal>& core : cores) {
449 if (core.size() == 1) {
450 for (
const int index : assumption_to_indices_.at(core.front().Index())) {
451 const IntegerVariable
var = normalized_objective_variables_[
index];
454 hs_model->mutable_variable(
index)->set_lower_bound(new_bound);
456 hs_model->mutable_variable(
index)->set_upper_bound(-new_bound);
463 MPConstraintProto* at_least_one = hs_model->add_constraint();
464 at_least_one->set_lower_bound(1.0);
465 for (
const Literal
lit : core) {
466 for (
const int index : assumption_to_indices_.at(
lit.Index())) {
467 const IntegerVariable
var = normalized_objective_variables_[
index];
474 const double hs_value =
475 std::round(response_.variable_value(
index)) * sign;
477 if (hs_value == sat_lb) {
478 at_least_one->add_var_index(
index);
479 at_least_one->add_coefficient(sign);
480 at_least_one->set_lower_bound(at_least_one->lower_bound() + hs_value);
484 const std::pair<int, int64_t> key = {
index,
485 static_cast<int64_t
>(hs_value)};
486 const int new_bool_var_index = hs_model->variable_size();
487 const auto [it, inserted] =
488 mp_integer_literals_.insert({key, new_bool_var_index});
490 at_least_one->add_var_index(it->second);
491 at_least_one->add_coefficient(1.0);
495 MPVariableProto* bool_var = hs_model->add_variable();
496 bool_var->set_lower_bound(0);
497 bool_var->set_upper_bound(1);
498 bool_var->set_is_integer(
true);
502 MPConstraintProto* implied_bound = hs_model->add_constraint();
503 implied_bound->set_lower_bound(sat_lb);
504 implied_bound->add_var_index(
index);
505 implied_bound->add_coefficient(sign);
506 implied_bound->add_var_index(new_bool_var_index);
507 implied_bound->add_coefficient(sat_lb - hs_value - 1.0);
515std::vector<Literal> HittingSetOptimizer::BuildAssumptions(
516 IntegerValue stratified_threshold,
517 IntegerValue* next_stratified_threshold) {
518 std::vector<Literal> assumptions;
521 for (
int i = 0;
i < normalized_objective_variables_.size(); ++
i) {
522 const IntegerVariable
var = normalized_objective_variables_[
i];
523 const IntegerValue coeff = normalized_objective_coefficients_[
i];
528 const IntegerValue hs_value(
529 static_cast<int64_t
>(std::round(response_.variable_value(
i))) *
536 if (coeff < stratified_threshold) {
537 *next_stratified_threshold = std::max(*next_stratified_threshold, coeff);
543 assumption_to_indices_[assumptions.back().Index()].push_back(
i);
554#if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
564 for (
int iter = 0;; ++iter) {
571 if (shared_response_ !=
nullptr) {
572 const IntegerValue best_lower_bound =
574 obj_constraint_->set_lower_bound(
ToDouble(best_lower_bound));
582 if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
593 if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
597 const IntegerValue mip_objective(
598 static_cast<int64_t
>(std::round(response_.objective_value())));
599 VLOG(2) <<
"--" << iter
600 <<
"-- constraints:" << request_.mutable_model()->constraint_size()
601 <<
" variables:" << request_.mutable_model()->variable_size()
602 <<
" hs_lower_bound:"
604 <<
" strat:" << stratified_threshold;
620 assumption_to_indices_.clear();
621 IntegerValue next_stratified_threshold(0);
622 const std::vector<Literal> assumptions =
623 BuildAssumptions(stratified_threshold, &next_stratified_threshold);
626 if (assumptions.empty() && next_stratified_threshold > 0) {
627 CHECK_LT(next_stratified_threshold, stratified_threshold);
628 stratified_threshold = next_stratified_threshold;
636 result = FindMultipleCoresForMaxHs(assumptions, &temp_cores_);
639 if (parameters_.stop_after_first_solution()) {
642 if (temp_cores_.empty()) {
645 stratified_threshold = next_stratified_threshold;
646 if (stratified_threshold == 0)
break;
660 AddCoresToTheMpModel(temp_cores_);
665 LOG(FATAL) <<
"Not supported.";
double GetDeterministicLimit() const
double GetElapsedDeterministicTime() const
void ChangeDeterministicLimit(double new_limit)
HittingSetOptimizer(const CpModelProto &model_proto, const ObjectiveDefinition &objective_definition, const std::function< void()> &feasible_solution_observer, Model *model)
SatSolver::Status Optimize()
IntegerVariable GetLiteralView(Literal lit) const
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
IntegerValue UpperBound(IntegerVariable i) const
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit)
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
void NotifyThatModelIsUnsat()
std::vector< Literal > GetLastIncompatibleDecisions()
void Backtrack(int target_level)
const SatParameters & parameters() const
void SetAssumptionLevel(int assumption_level)
IntegerValue GetInnerObjectiveLowerBound()
void resize(size_type new_size)
absl::Span< const double > coefficients
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(const std::vector< 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)
std::vector< std::vector< Literal > > at_most_ones
std::vector< LinearConstraint > linear_constraints
IntegerVariable objective_var
std::vector< IntegerVariable > vars
double ScaleIntegerObjective(IntegerValue value) const
std::vector< IntegerValue > coeffs