24#include "absl/container/flat_hash_map.h"
25#include "absl/container/flat_hash_set.h"
26#include "absl/flags/flag.h"
27#include "absl/meta/type_traits.h"
28#include "absl/random/random.h"
29#include "absl/strings/string_view.h"
33#if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
36#include "ortools/linear_solver/linear_solver.pb.h"
37#include "ortools/sat/cp_model.pb.h"
46#include "ortools/sat/sat_parameters.pb.h"
55 int, max_hs_strategy, 0,
56 "MaxHsStrategy: 0 extract only objective variable, 1 extract all variables "
57 "colocated with objective variables, 2 extract all variables in the "
66 const std::function<
void()>& feasible_solution_observer,
Model*
model)
68 objective_definition_(objective_definition),
69 feasible_solution_observer_(feasible_solution_observer),
73 parameters_(*
model->GetOrCreate<SatParameters>()),
78 request_.set_solver_specific_parameters(
"limits/gap = 0");
79 request_.set_solver_type(MPModelRequest::SCIP_MIXED_INTEGER_PROGRAMMING);
82bool HittingSetOptimizer::ImportFromOtherWorkers() {
84 for (
const auto& cb : level_zero_callbacks->callbacks) {
96 std::vector<Literal> assumptions,
97 std::vector<std::vector<Literal>>* cores) {
104 bool first_loop =
true;
110 std::shuffle(assumptions.begin(), assumptions.end(), *random_);
116 if (sat_solver_->
parameters().minimize_core()) {
119 CHECK(!core.empty());
120 cores->push_back(core);
121 if (!parameters_.find_multiple_cores())
break;
125 CHECK(!core.empty());
126 const Literal random_literal =
127 core[absl::Uniform<int>(*random_, 0, core.size())];
128 for (
int i = 0; i < assumptions.size(); ++i) {
129 if (assumptions[i] == random_literal) {
130 std::swap(assumptions[i], assumptions.back());
131 assumptions.pop_back();
143 }
while (!assumptions.empty());
148int HittingSetOptimizer::GetExtractedIndex(IntegerVariable
var)
const {
149 if (
var.value() >= sat_var_to_mp_var_.
size())
return kUnextracted;
150 return sat_var_to_mp_var_[
var];
153void HittingSetOptimizer::ExtractObjectiveVariables() {
154 const std::vector<IntegerVariable>& variables = objective_definition_.
vars;
156 MPModelProto* hs_model = request_.mutable_model();
160 if (obj_constraint_ ==
nullptr) {
161 obj_constraint_ = hs_model->add_constraint();
162 obj_constraint_->set_lower_bound(-std::numeric_limits<double>::infinity());
163 obj_constraint_->set_upper_bound(std::numeric_limits<double>::infinity());
167 for (
int i = 0; i < variables.size(); ++i) {
168 IntegerVariable
var = variables[i];
179 normalized_objective_variables_.push_back(
var);
180 normalized_objective_coefficients_.push_back(
coeff);
183 normalized_objective_coefficients_.push_back(-
coeff);
187 const int index = hs_model->variable_size();
188 obj_constraint_->add_var_index(
index);
191 MPVariableProto* var_proto = hs_model->add_variable();
195 var_proto->set_is_integer(
true);
199 if (max_index >= sat_var_to_mp_var_.
size()) {
200 sat_var_to_mp_var_.
resize(max_index + 1, -1);
204 extracted_variables_info_.push_back({
var, var_proto});
208void HittingSetOptimizer::ExtractAdditionalVariables(
209 const std::vector<IntegerVariable>& to_extract) {
210 MPModelProto* hs_model = request_.mutable_model();
212 VLOG(2) <<
"Extract " << to_extract.size() <<
" additional variables";
213 for (IntegerVariable tmp_var : to_extract) {
214 if (GetExtractedIndex(tmp_var) != kUnextracted)
continue;
219 const int index = hs_model->variable_size();
220 MPVariableProto* var_proto = hs_model->add_variable();
223 var_proto->set_is_integer(
true);
227 if (max_index >= sat_var_to_mp_var_.
size()) {
228 sat_var_to_mp_var_.
resize(max_index + 1, -1);
232 extracted_variables_info_.push_back({
var, var_proto});
243std::vector<IntegerVariable>
244HittingSetOptimizer::ComputeAdditionalVariablesToExtract() {
245 absl::flat_hash_set<IntegerVariable> result_set;
246 if (absl::GetFlag(FLAGS_max_hs_strategy) == 0)
return {};
247 const bool extract_all = absl::GetFlag(FLAGS_max_hs_strategy) == 2;
249 for (
const std::vector<Literal>& literals : relaxation_.
at_most_ones) {
250 bool found_at_least_one = extract_all;
251 for (
const Literal
literal : literals) {
254 found_at_least_one =
true;
256 if (found_at_least_one)
break;
258 if (!found_at_least_one)
continue;
259 for (
const Literal
literal : literals) {
261 if (GetExtractedIndex(
var) == kUnextracted) {
268 bool found_at_least_one = extract_all;
269 for (
const IntegerVariable
var : linear.vars) {
270 if (GetExtractedIndex(
var) != kUnextracted) {
271 found_at_least_one =
true;
273 if (found_at_least_one)
break;
275 if (!found_at_least_one)
continue;
276 for (
const IntegerVariable
var : linear.vars) {
277 if (GetExtractedIndex(
var) == kUnextracted) {
283 std::vector<IntegerVariable> result(result_set.begin(), result_set.end());
284 std::sort(result.begin(), result.end());
289void HittingSetOptimizer::ProjectAndAddAtMostOne(
290 const std::vector<Literal>& literals) {
291 LinearConstraintBuilder builder(model_, 0, 1);
292 for (
const Literal&
literal : literals) {
293 if (!builder.AddLiteralTerm(
literal, 1)) {
294 VLOG(3) <<
"Could not extract literal " <<
literal;
298 if (ProjectAndAddLinear(builder.Build()) !=
nullptr) {
299 num_extracted_at_most_ones_++;
303MPConstraintProto* HittingSetOptimizer::ProjectAndAddLinear(
304 const LinearConstraint& linear) {
305 int num_extracted_variables = 0;
306 for (
int i = 0; i < linear.vars.size(); ++i) {
308 num_extracted_variables++;
311 if (num_extracted_variables <= 1)
return nullptr;
313 MPConstraintProto*
ct = request_.mutable_model()->add_constraint();
314 ProjectLinear(linear,
ct);
318void HittingSetOptimizer::ProjectLinear(
const LinearConstraint& linear,
319 MPConstraintProto*
ct) {
320 IntegerValue lb = linear.lb;
321 IntegerValue ub = linear.ub;
323 for (
int i = 0; i < linear.vars.size(); ++i) {
324 const IntegerVariable
var = linear.vars[i];
325 const IntegerValue
coeff = linear.coeffs[i];
328 if (
index != kUnextracted) {
349bool HittingSetOptimizer::ComputeInitialMpModel() {
350 if (!ImportFromOtherWorkers())
return false;
352 ExtractObjectiveVariables();
355 for (
const auto&
ct : model_proto_.constraints()) {
357 model_, &relaxation_);
360 ExtractAdditionalVariables(ComputeAdditionalVariablesToExtract());
364 ProjectAndAddAtMostOne(literals);
366 if (num_extracted_at_most_ones_ > 0) {
367 VLOG(2) <<
"Projected " << num_extracted_at_most_ones_ <<
"/"
368 << relaxation_.
at_most_ones.size() <<
" at_most_ones constraints";
372 MPConstraintProto*
ct =
374 if (
ct !=
nullptr) linear_extract_info_.push_back({i,
ct});
376 if (!linear_extract_info_.empty()) {
377 VLOG(2) <<
"Projected " << linear_extract_info_.size() <<
"/"
383void HittingSetOptimizer::TightenMpModel() {
385 for (
const auto& [
var, var_proto] : extracted_variables_info_) {
391 for (
const auto& [
index,
ct] : linear_extract_info_) {
392 const double original_lb =
ct->lower_bound();
393 const double original_ub =
ct->upper_bound();
396 if (original_lb !=
ct->lower_bound() || original_ub !=
ct->upper_bound()) {
401 VLOG(2) <<
"Tightened " << tightened <<
" linear constraints";
405bool HittingSetOptimizer::ProcessSolution() {
406 const std::vector<IntegerVariable>& variables = objective_definition_.
vars;
411 IntegerValue objective(0);
412 for (
int i = 0; i < variables.size(); ++i) {
421 if (feasible_solution_observer_ !=
nullptr) {
422 feasible_solution_observer_();
438void HittingSetOptimizer::AddCoresToTheMpModel(
439 const std::vector<std::vector<Literal>>& cores) {
440 MPModelProto* hs_model = request_.mutable_model();
442 for (
const std::vector<Literal>& core : cores) {
444 if (core.size() == 1) {
445 for (
const int index : assumption_to_indices_.at(core.front().Index())) {
446 const IntegerVariable
var = normalized_objective_variables_[
index];
449 hs_model->mutable_variable(
index)->set_lower_bound(new_bound);
451 hs_model->mutable_variable(
index)->set_upper_bound(-new_bound);
458 MPConstraintProto* at_least_one = hs_model->add_constraint();
459 at_least_one->set_lower_bound(1.0);
460 for (
const Literal lit : core) {
461 for (
const int index : assumption_to_indices_.at(lit.Index())) {
462 const IntegerVariable
var = normalized_objective_variables_[
index];
469 const double hs_value =
470 std::round(response_.variable_value(
index)) * sign;
472 if (hs_value == sat_lb) {
473 at_least_one->add_var_index(
index);
474 at_least_one->add_coefficient(sign);
475 at_least_one->set_lower_bound(at_least_one->lower_bound() + hs_value);
479 const std::pair<int, int64_t> key = {
index,
480 static_cast<int64_t
>(hs_value)};
481 const int new_bool_var_index = hs_model->variable_size();
482 const auto [it, inserted] =
483 mp_integer_literals_.insert({key, new_bool_var_index});
485 at_least_one->add_var_index(it->second);
486 at_least_one->add_coefficient(1.0);
490 MPVariableProto* bool_var = hs_model->add_variable();
491 bool_var->set_lower_bound(0);
492 bool_var->set_upper_bound(1);
493 bool_var->set_is_integer(
true);
497 MPConstraintProto* implied_bound = hs_model->add_constraint();
498 implied_bound->set_lower_bound(sat_lb);
499 implied_bound->add_var_index(
index);
500 implied_bound->add_coefficient(sign);
501 implied_bound->add_var_index(new_bool_var_index);
502 implied_bound->add_coefficient(sat_lb - hs_value - 1.0);
510std::vector<Literal> HittingSetOptimizer::BuildAssumptions(
511 IntegerValue stratified_threshold,
512 IntegerValue* next_stratified_threshold) {
513 std::vector<Literal> assumptions;
516 for (
int i = 0; i < normalized_objective_variables_.size(); ++i) {
517 const IntegerVariable
var = normalized_objective_variables_[i];
518 const IntegerValue
coeff = normalized_objective_coefficients_[i];
523 const IntegerValue hs_value(
524 static_cast<int64_t
>(std::round(response_.variable_value(i))) *
531 if (
coeff < stratified_threshold) {
532 *next_stratified_threshold =
std::max(*next_stratified_threshold,
coeff);
538 assumption_to_indices_[assumptions.back().Index()].push_back(i);
549#if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
559 for (
int iter = 0;; ++iter) {
566 if (shared_response_ !=
nullptr) {
567 const IntegerValue best_lower_bound =
569 obj_constraint_->set_lower_bound(
ToDouble(best_lower_bound));
577 if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
588 if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
592 const IntegerValue mip_objective(
593 static_cast<int64_t
>(std::round(response_.objective_value())));
594 VLOG(2) <<
"--" << iter
595 <<
"-- constraints:" << request_.mutable_model()->constraint_size()
596 <<
" variables:" << request_.mutable_model()->variable_size()
597 <<
" hs_lower_bound:"
599 <<
" strat:" << stratified_threshold;
615 assumption_to_indices_.clear();
616 IntegerValue next_stratified_threshold(0);
617 const std::vector<Literal> assumptions =
618 BuildAssumptions(stratified_threshold, &next_stratified_threshold);
621 if (assumptions.empty() && next_stratified_threshold > 0) {
622 CHECK_LT(next_stratified_threshold, stratified_threshold);
623 stratified_threshold = next_stratified_threshold;
631 result = FindMultipleCoresForMaxHs(assumptions, &temp_cores_);
634 if (parameters_.stop_after_first_solution()) {
637 if (temp_cores_.empty()) {
640 stratified_threshold = next_stratified_threshold;
641 if (stratified_threshold == 0)
break;
655 AddCoresToTheMpModel(temp_cores_);
#define CHECK_LT(val1, val2)
#define VLOG(verboselevel)
void resize(size_type new_size)
static void SolveWithProto(const MPModelRequest &model_request, MPSolutionResponse *response, std::atomic< bool > *interrupt=nullptr)
Solves the model encoded by a MPModelRequest protocol buffer and fills the solution encoded as a MPSo...
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
double GetDeterministicLimit() const
Queries the deterministic time limit.
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
double GetElapsedDeterministicTime() const
Returns the elapsed deterministic time since the construction of this object.
void ChangeDeterministicLimit(double new_limit)
Overwrites the deterministic time limit with the new value.
SatSolver::Status Optimize()
HittingSetOptimizer(const CpModelProto &model_proto, const ObjectiveDefinition &objective_definition, const std::function< void()> &feasible_solution_observer, Model *model)
const IntegerVariable GetLiteralView(Literal lit) const
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
IntegerValue UpperBound(IntegerVariable i) const
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
IntegerValue LowerBound(IntegerVariable i) const
Class that owns everything related to a particular optimization model.
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
const SatParameters & parameters() const
void NotifyThatModelIsUnsat()
void SetAssumptionLevel(int assumption_level)
void Backtrack(int target_level)
std::vector< Literal > GetLastIncompatibleDecisions()
IntegerValue GetInnerObjectiveLowerBound()
CpModelProto const * model_proto
absl::Span< const double > coefficients
A C++ wrapper that provides a simple and unified interface to several linear programming and mixed in...
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")
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
void TryToLinearizeConstraint(const CpModelProto &model_proto, const ConstraintProto &ct, int linearization_level, Model *model, LinearRelaxation *relaxation)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
IntegerVariable PositiveVariable(IntegerVariable i)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
bool VariableIsPositive(IntegerVariable i)
double ToDouble(IntegerValue value)
Collection of objects used to extend the Constraint Solver library.
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
std::vector< std::vector< Literal > > at_most_ones
std::vector< LinearConstraint > linear_constraints
std::vector< IntegerValue > coeffs
std::vector< IntegerVariable > vars
IntegerVariable objective_var
double ScaleIntegerObjective(IntegerValue value) const