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);
82 bool 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());
148 int 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];
153 void 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);
189 obj_constraint_->add_coefficient(
ToDouble(coeff));
191 MPVariableProto* var_proto = hs_model->add_variable();
194 var_proto->set_objective_coefficient(
ToDouble(coeff));
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});
208 void 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});
243 std::vector<IntegerVariable>
244 HittingSetOptimizer::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());
289 void 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_++;
303 MPConstraintProto* 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);
318 void 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) {
349 bool HittingSetOptimizer::ComputeInitialMpModel() {
350 if (!ImportFromOtherWorkers())
return false;
352 ExtractObjectiveVariables();
355 ActivityBoundHelper activity_bound_helper;
356 activity_bound_helper.AddAllAtMostOnes(model_proto_);
357 for (
const auto&
ct : model_proto_.constraints()) {
359 model_, &relaxation_, &activity_bound_helper);
362 ExtractAdditionalVariables(ComputeAdditionalVariablesToExtract());
366 ProjectAndAddAtMostOne(literals);
368 if (num_extracted_at_most_ones_ > 0) {
369 VLOG(2) <<
"Projected " << num_extracted_at_most_ones_ <<
"/"
370 << relaxation_.
at_most_ones.size() <<
" at_most_ones constraints";
374 MPConstraintProto*
ct =
376 if (
ct !=
nullptr) linear_extract_info_.push_back({i,
ct});
378 if (!linear_extract_info_.empty()) {
379 VLOG(2) <<
"Projected " << linear_extract_info_.size() <<
"/"
385 void HittingSetOptimizer::TightenMpModel() {
387 for (
const auto& [
var, var_proto] : extracted_variables_info_) {
393 for (
const auto& [
index,
ct] : linear_extract_info_) {
394 const double original_lb =
ct->lower_bound();
395 const double original_ub =
ct->upper_bound();
398 if (original_lb !=
ct->lower_bound() || original_ub !=
ct->upper_bound()) {
403 VLOG(2) <<
"Tightened " << tightened <<
" linear constraints";
407 bool HittingSetOptimizer::ProcessSolution() {
408 const std::vector<IntegerVariable>& variables = objective_definition_.
vars;
413 IntegerValue objective(0);
414 for (
int i = 0; i < variables.size(); ++i) {
423 if (feasible_solution_observer_ !=
nullptr) {
424 feasible_solution_observer_();
440 void HittingSetOptimizer::AddCoresToTheMpModel(
441 const std::vector<std::vector<Literal>>& cores) {
442 MPModelProto* hs_model = request_.mutable_model();
444 for (
const std::vector<Literal>& core : cores) {
446 if (core.size() == 1) {
447 for (
const int index : assumption_to_indices_.at(core.front().Index())) {
448 const IntegerVariable
var = normalized_objective_variables_[
index];
451 hs_model->mutable_variable(
index)->set_lower_bound(new_bound);
453 hs_model->mutable_variable(
index)->set_upper_bound(-new_bound);
460 MPConstraintProto* at_least_one = hs_model->add_constraint();
461 at_least_one->set_lower_bound(1.0);
462 for (
const Literal lit : core) {
463 for (
const int index : assumption_to_indices_.at(lit.Index())) {
464 const IntegerVariable
var = normalized_objective_variables_[
index];
471 const double hs_value =
472 std::round(response_.variable_value(
index)) * sign;
474 if (hs_value == sat_lb) {
475 at_least_one->add_var_index(
index);
476 at_least_one->add_coefficient(sign);
477 at_least_one->set_lower_bound(at_least_one->lower_bound() + hs_value);
481 const std::pair<int, int64_t> key = {
index,
482 static_cast<int64_t
>(hs_value)};
483 const int new_bool_var_index = hs_model->variable_size();
484 const auto [it, inserted] =
485 mp_integer_literals_.insert({key, new_bool_var_index});
487 at_least_one->add_var_index(it->second);
488 at_least_one->add_coefficient(1.0);
492 MPVariableProto* bool_var = hs_model->add_variable();
493 bool_var->set_lower_bound(0);
494 bool_var->set_upper_bound(1);
495 bool_var->set_is_integer(
true);
499 MPConstraintProto* implied_bound = hs_model->add_constraint();
500 implied_bound->set_lower_bound(sat_lb);
501 implied_bound->add_var_index(
index);
502 implied_bound->add_coefficient(sign);
503 implied_bound->add_var_index(new_bool_var_index);
504 implied_bound->add_coefficient(sat_lb - hs_value - 1.0);
512 std::vector<Literal> HittingSetOptimizer::BuildAssumptions(
513 IntegerValue stratified_threshold,
514 IntegerValue* next_stratified_threshold) {
515 std::vector<Literal> assumptions;
518 for (
int i = 0; i < normalized_objective_variables_.size(); ++i) {
519 const IntegerVariable
var = normalized_objective_variables_[i];
520 const IntegerValue coeff = normalized_objective_coefficients_[i];
525 const IntegerValue hs_value(
526 static_cast<int64_t
>(std::round(response_.variable_value(i))) *
533 if (coeff < stratified_threshold) {
534 *next_stratified_threshold =
std::max(*next_stratified_threshold, coeff);
540 assumption_to_indices_[assumptions.back().Index()].push_back(i);
551 #if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
561 for (
int iter = 0;; ++iter) {
568 if (shared_response_ !=
nullptr) {
569 const IntegerValue best_lower_bound =
571 obj_constraint_->set_lower_bound(
ToDouble(best_lower_bound));
579 if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
590 if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
594 const IntegerValue mip_objective(
595 static_cast<int64_t
>(std::round(response_.objective_value())));
596 VLOG(2) <<
"--" << iter
597 <<
"-- constraints:" << request_.mutable_model()->constraint_size()
598 <<
" variables:" << request_.mutable_model()->variable_size()
599 <<
" hs_lower_bound:"
601 <<
" strat:" << stratified_threshold;
617 assumption_to_indices_.clear();
618 IntegerValue next_stratified_threshold(0);
619 const std::vector<Literal> assumptions =
620 BuildAssumptions(stratified_threshold, &next_stratified_threshold);
623 if (assumptions.empty() && next_stratified_threshold > 0) {
624 CHECK_LT(next_stratified_threshold, stratified_threshold);
625 stratified_threshold = next_stratified_threshold;
633 result = FindMultipleCoresForMaxHs(assumptions, &temp_cores_);
636 if (parameters_.stop_after_first_solution()) {
639 if (temp_cores_.empty()) {
642 stratified_threshold = next_stratified_threshold;
643 if (stratified_threshold == 0)
break;
657 AddCoresToTheMpModel(temp_cores_);
662 LOG(FATAL) <<
"Not supported.";
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)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
IntegerVariable PositiveVariable(IntegerVariable i)
void TryToLinearizeConstraint(const CpModelProto &model_proto, const ConstraintProto &ct, int linearization_level, Model *model, LinearRelaxation *relaxation, ActivityBoundHelper *activity_helper)
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
#define VLOG(verboselevel)