23#include "absl/container/flat_hash_map.h"
24#include "absl/log/check.h"
25#include "absl/meta/type_traits.h"
28#include "ortools/glop/parameters.pb.h"
41#include "ortools/sat/sat_parameters.pb.h"
58const double FeasibilityPump::kCpEpsilon = 1e-4;
61 : sat_parameters_(*(model->GetOrCreate<SatParameters>())),
62 time_limit_(model->GetOrCreate<
TimeLimit>()),
64 trail_(model->GetOrCreate<
Trail>()),
67 sat_solver_(model->GetOrCreate<
SatSolver>()),
71 glop::GlopParameters parameters;
75 parameters.set_use_dual_simplex(
false);
76 parameters.set_max_number_of_iterations(2000);
77 simplex_.SetParameters(parameters);
83 VLOG(1) <<
"Feasibility Pump Total number of simplex iterations: "
84 << total_num_simplex_iterations_;
89 for (
const IntegerVariable var : ct.
VarsAsSpan()) {
93 integer_lp_.push_back(LinearConstraintInternal());
94 LinearConstraintInternal& new_ct = integer_lp_.back();
97 CHECK_LE(ct.
lb, ct.
ub);
100 IntegerVariable var = ct.
vars[
i];
101 IntegerValue coeff = ct.
coeffs[
i];
106 new_ct.terms.push_back({GetOrCreateMirrorVariable(var), coeff});
109 std::sort(new_ct.terms.begin(), new_ct.terms.end());
113 IntegerValue coeff) {
114 objective_is_defined_ =
true;
115 const IntegerVariable pos_var =
117 if (ivar != pos_var) coeff = -coeff;
119 const auto it = mirror_lp_variable_.find(pos_var);
120 if (it == mirror_lp_variable_.end())
return;
121 const ColIndex col = it->second;
122 integer_objective_.push_back({col, coeff});
123 objective_infinity_norm_ =
124 std::max(objective_infinity_norm_,
IntTypeAbs(coeff));
127ColIndex FeasibilityPump::GetOrCreateMirrorVariable(
128 IntegerVariable positive_variable) {
131 const auto it = mirror_lp_variable_.find(positive_variable);
132 if (it == mirror_lp_variable_.end()) {
133 const int model_var =
135 model_vars_size_ = std::max(model_vars_size_, model_var + 1);
137 const ColIndex col(integer_variables_.size());
138 mirror_lp_variable_[positive_variable] = col;
139 integer_variables_.push_back(positive_variable);
140 var_is_binary_.push_back(
false);
141 lp_solution_.push_back(std::numeric_limits<double>::infinity());
142 integer_solution_.push_back(0);
149void FeasibilityPump::PrintStats() {
150 if (lp_solution_is_set_) {
151 VLOG(2) <<
"Fractionality: " << lp_solution_fractionality_;
153 VLOG(2) <<
"Fractionality: NA";
154 VLOG(2) <<
"simplex status: " << simplex_.GetProblemStatus();
157 if (integer_solution_is_set_) {
158 VLOG(2) <<
"#Infeasible const: " << num_infeasible_constraints_;
159 VLOG(2) <<
"Infeasibility: " << integer_solution_infeasibility_;
161 VLOG(2) <<
"Infeasibility: NA";
166 if (lp_data_.num_variables() == 0) {
167 InitializeWorkingLP();
169 UpdateBoundsOfLpVariables();
170 lp_solution_is_set_ =
false;
171 integer_solution_is_set_ =
false;
174 for (ColIndex col(0); col < lp_data_.num_variables(); ++col) {
175 lp_data_.SetObjectiveCoefficient(col, 0.0);
177 for (
const auto& term : integer_objective_) {
178 lp_data_.SetObjectiveCoefficient(term.first,
ToDouble(term.second));
181 mixing_factor_ = 1.0;
182 for (
int i = 0;
i < max_fp_iterations_; ++
i) {
183 if (time_limit_->LimitReached())
break;
184 L1DistanceMinimize();
185 if (!SolveLp())
break;
186 if (lp_solution_is_integer_)
break;
190 if (integer_solution_is_feasible_) MaybePushToRepo();
193 if (sat_solver_->ModelIsUnsat())
return false;
200void FeasibilityPump::MaybePushToRepo() {
201 if (incomplete_solutions_ ==
nullptr)
return;
202 const double kInfinity = std::numeric_limits<double>::infinity();
205 if (lp_solution_is_integer_) {
207 tmp_solution_.assign(model_vars_size_,
kInfinity);
208 for (
const IntegerVariable positive_var : integer_variables_) {
209 const int model_var =
211 if (model_var >= 0 && model_var < model_vars_size_) {
218 if (integer_solution_is_feasible_) {
220 tmp_solution_.assign(model_vars_size_,
kInfinity);
221 for (
const IntegerVariable positive_var : integer_variables_) {
222 const int model_var =
223 mapping_->GetProtoVariableFromIntegerVariable(positive_var);
224 if (model_var >= 0 && model_var < model_vars_size_) {
228 incomplete_solutions_->AddSolution(tmp_solution_);
236void FeasibilityPump::InitializeWorkingLP() {
239 for (
int i = 0;
i < integer_variables_.size(); ++
i) {
240 CHECK_EQ(ColIndex(
i), lp_data_.CreateNewVariable());
241 lp_data_.SetVariableType(ColIndex(
i),
246 for (
const LinearConstraintInternal& ct : integer_lp_) {
249 for (
const auto& term : ct.terms) {
250 lp_data_.SetCoefficient(row, term.first,
ToDouble(term.second));
255 for (
const auto& term : integer_objective_) {
256 lp_data_.SetObjectiveCoefficient(term.first,
ToDouble(term.second));
259 const int num_vars = integer_variables_.size();
260 for (
int i = 0;
i < num_vars;
i++) {
261 const IntegerVariable cp_var = integer_variables_[
i];
262 const double lb =
ToDouble(integer_trail_->LevelZeroLowerBound(cp_var));
263 const double ub =
ToDouble(integer_trail_->LevelZeroUpperBound(cp_var));
264 lp_data_.SetVariableBounds(ColIndex(
i), lb, ub);
267 objective_normalization_factor_ = 0.0;
269 const ColIndex num_cols = lp_data_.num_variables();
270 for (ColIndex col : lp_data_.IntegerVariablesList()) {
271 var_is_binary_[col.value()] = lp_data_.IsVariableBinary(col);
272 if (!var_is_binary_[col.value()]) {
273 integer_variables.push_back(col);
278 objective_normalization_factor_ +=
279 std::abs(lp_data_.GetObjectiveCoefficientForMinimizationVersion(col));
281 CHECK_GT(lp_data_.IntegerVariablesList().size(), 0);
282 objective_normalization_factor_ =
283 objective_normalization_factor_ / lp_data_.IntegerVariablesList().size();
285 if (!integer_variables.empty()) {
287 norm_variables_.assign(num_cols, ColIndex(-1));
288 norm_lhs_constraints_.assign(num_cols, RowIndex(-1));
289 norm_rhs_constraints_.assign(num_cols, RowIndex(-1));
306 for (
const ColIndex col : integer_variables) {
307 const ColIndex norm_variable = lp_data_.CreateNewVariable();
308 norm_variables_[col] = norm_variable;
310 const RowIndex row_a = lp_data_.CreateNewConstraint();
311 norm_lhs_constraints_[col] = row_a;
312 lp_data_.SetCoefficient(row_a, norm_variable, 1.0);
313 lp_data_.SetCoefficient(row_a, col, -1.0);
314 const RowIndex row_b = lp_data_.CreateNewConstraint();
315 norm_rhs_constraints_[col] = row_b;
316 lp_data_.SetCoefficient(row_b, norm_variable, 1.0);
317 lp_data_.SetCoefficient(row_b, col, 1.0);
321 scaler_.Scale(&lp_data_);
322 lp_data_.AddSlackVariablesWhereNecessary(
326void FeasibilityPump::L1DistanceMinimize() {
327 std::vector<double> new_obj_coeffs(lp_data_.num_variables().value(), 0.0);
331 const ColIndex num_cols(lp_data_.objective_coefficients().size());
332 for (ColIndex col(0); col < num_cols; ++col) {
333 new_obj_coeffs[col.value()] =
334 mixing_factor_ * lp_data_.objective_coefficients()[col];
339 for (
const ColIndex col : lp_data_.IntegerVariablesList()) {
340 if (var_is_binary_[col.value()]) {
342 mixing_factor_ * lp_data_.objective_coefficients()[col] +
343 (1 - mixing_factor_) * objective_normalization_factor_ *
344 (1 - 2 * integer_solution_[col.value()]);
345 new_obj_coeffs[col.value()] = objective_coefficient;
357 (1 - mixing_factor_) * objective_normalization_factor_;
358 new_obj_coeffs[norm_variables_[col].value()] = objective_coefficient;
362 const ColIndex norm_lhs_slack_variable =
363 lp_data_.GetSlackVariable(norm_lhs_constraints_[col]);
364 const double lhs_scaling_factor =
365 scaler_.VariableScalingFactorWithSlack(norm_lhs_slack_variable);
366 lp_data_.SetVariableBounds(
368 lhs_scaling_factor * integer_solution_[col.value()]);
369 const ColIndex norm_rhs_slack_variable =
370 lp_data_.GetSlackVariable(norm_rhs_constraints_[col]);
371 const double rhs_scaling_factor =
372 scaler_.VariableScalingFactorWithSlack(norm_rhs_slack_variable);
373 lp_data_.SetVariableBounds(
375 -rhs_scaling_factor * integer_solution_[col.value()]);
378 for (ColIndex col(0); col < lp_data_.num_variables(); ++col) {
379 lp_data_.SetObjectiveCoefficient(col, new_obj_coeffs[col.value()]);
382 mixing_factor_ *= 0.8;
385bool FeasibilityPump::SolveLp() {
386 const int num_vars = integer_variables_.size();
387 VLOG(3) <<
"LP relaxation: " << lp_data_.GetDimensionString() <<
".";
389 const auto status = simplex_.Solve(lp_data_, time_limit_);
390 total_num_simplex_iterations_ += simplex_.GetNumberOfIterations();
392 VLOG(1) <<
"The LP solver encountered an error: " << status.error_message();
393 simplex_.ClearStateForNextSolve();
400 VLOG(3) <<
"simplex status: " << simplex_.GetProblemStatus();
405 lp_solution_fractionality_ = 0.0;
410 lp_solution_is_set_ =
true;
411 for (
int i = 0;
i < num_vars;
i++) {
412 const double value = GetVariableValueAtCpScale(ColIndex(
i));
413 lp_solution_[
i] = value;
414 lp_solution_fractionality_ = std::max(
415 lp_solution_fractionality_, std::abs(value - std::round(value)));
420 for (
const auto& term : integer_objective_) {
421 lp_objective_ += lp_solution_[term.first.value()] * term.second.value();
423 lp_solution_is_integer_ = lp_solution_fractionality_ < kCpEpsilon;
428void FeasibilityPump::UpdateBoundsOfLpVariables() {
429 const int num_vars = integer_variables_.size();
430 for (
int i = 0;
i < num_vars;
i++) {
431 const IntegerVariable cp_var = integer_variables_[
i];
432 const double lb =
ToDouble(integer_trail_->LevelZeroLowerBound(cp_var));
433 const double ub =
ToDouble(integer_trail_->LevelZeroUpperBound(cp_var));
434 const double factor = scaler_.VariableScalingFactor(ColIndex(
i));
435 lp_data_.SetVariableBounds(ColIndex(
i), lb * factor, ub * factor);
440 return lp_solution_[mirror_lp_variable_.at(variable).value()];
443double FeasibilityPump::GetVariableValueAtCpScale(ColIndex var) {
452 IntegerVariable variable)
const {
453 return integer_solution_[mirror_lp_variable_.at(variable).value()];
456bool FeasibilityPump::Round() {
457 bool rounding_successful =
true;
458 if (sat_parameters_.fp_rounding() == SatParameters::NEAREST_INTEGER) {
459 rounding_successful = NearestIntegerRounding();
460 }
else if (sat_parameters_.fp_rounding() == SatParameters::LOCK_BASED) {
461 rounding_successful = LockBasedRounding();
462 }
else if (sat_parameters_.fp_rounding() ==
463 SatParameters::ACTIVE_LOCK_BASED) {
464 rounding_successful = ActiveLockBasedRounding();
465 }
else if (sat_parameters_.fp_rounding() ==
466 SatParameters::PROPAGATION_ASSISTED) {
467 rounding_successful = PropagationRounding();
469 if (!rounding_successful)
return false;
470 FillIntegerSolutionStats();
474bool FeasibilityPump::NearestIntegerRounding() {
475 if (!lp_solution_is_set_)
return false;
476 for (
int i = 0;
i < lp_solution_.size(); ++
i) {
477 integer_solution_[
i] =
static_cast<int64_t
>(std::round(lp_solution_[
i]));
479 integer_solution_is_set_ =
true;
483bool FeasibilityPump::LockBasedRounding() {
484 if (!lp_solution_is_set_)
return false;
485 const int num_vars = integer_variables_.size();
489 if (var_up_locks_.empty()) {
490 var_up_locks_.resize(num_vars, 0);
491 var_down_locks_.resize(num_vars, 0);
492 for (
int i = 0;
i < num_vars; ++
i) {
495 const bool constraint_upper_bounded =
498 const bool constraint_lower_bounded =
501 if (entry.coefficient() > 0) {
502 var_up_locks_[
i] += constraint_upper_bounded;
503 var_down_locks_[
i] += constraint_lower_bounded;
505 var_up_locks_[
i] += constraint_lower_bounded;
506 var_down_locks_[
i] += constraint_upper_bounded;
512 for (
int i = 0;
i < lp_solution_.size(); ++
i) {
513 if (std::abs(lp_solution_[
i] - std::round(lp_solution_[
i])) < 0.1 ||
514 var_up_locks_[
i] == var_down_locks_[
i]) {
515 integer_solution_[
i] =
static_cast<int64_t
>(std::round(lp_solution_[
i]));
516 }
else if (var_up_locks_[
i] > var_down_locks_[
i]) {
517 integer_solution_[
i] =
static_cast<int64_t
>(std::floor(lp_solution_[
i]));
519 integer_solution_[
i] =
static_cast<int64_t
>(std::ceil(lp_solution_[
i]));
522 integer_solution_is_set_ =
true;
526bool FeasibilityPump::ActiveLockBasedRounding() {
527 if (!lp_solution_is_set_)
return false;
528 const int num_vars = integer_variables_.size();
533 for (
int i = 0;
i < num_vars; ++
i) {
534 if (std::abs(lp_solution_[
i] - std::round(lp_solution_[
i])) < 0.1) {
535 integer_solution_[
i] =
static_cast<int64_t
>(std::round(lp_solution_[
i]));
540 for (
const auto entry : lp_data_.GetSparseColumn(ColIndex(
i))) {
542 simplex_.GetConstraintStatus(entry.row());
543 if (row_status == ConstraintStatus::AT_LOWER_BOUND) {
544 if (entry.coefficient() > 0) {
549 }
else if (row_status == ConstraintStatus::AT_UPPER_BOUND) {
550 if (entry.coefficient() > 0) {
557 if (up_locks == down_locks) {
558 integer_solution_[
i] =
static_cast<int64_t
>(std::round(lp_solution_[
i]));
559 }
else if (up_locks > down_locks) {
560 integer_solution_[
i] =
static_cast<int64_t
>(std::floor(lp_solution_[
i]));
562 integer_solution_[
i] =
static_cast<int64_t
>(std::ceil(lp_solution_[
i]));
566 integer_solution_is_set_ =
true;
570bool FeasibilityPump::PropagationRounding() {
571 if (!lp_solution_is_set_)
return false;
572 if (!sat_solver_->ResetToLevelZero())
return false;
575 std::vector<int> rounding_order;
577 std::vector<std::pair<double, int>> binary_fractionality_vars;
578 std::vector<std::pair<double, int>> general_fractionality_vars;
579 for (
int i = 0;
i < lp_solution_.size(); ++
i) {
580 const double fractionality =
581 std::abs(std::round(lp_solution_[
i]) - lp_solution_[
i]);
582 if (var_is_binary_[
i]) {
583 binary_fractionality_vars.push_back({fractionality,
i});
585 general_fractionality_vars.push_back({fractionality,
i});
588 std::sort(binary_fractionality_vars.begin(),
589 binary_fractionality_vars.end());
590 std::sort(general_fractionality_vars.begin(),
591 general_fractionality_vars.end());
593 for (
int i = 0;
i < binary_fractionality_vars.size(); ++
i) {
594 rounding_order.push_back(binary_fractionality_vars[
i].second);
596 for (
int i = 0;
i < general_fractionality_vars.size(); ++
i) {
597 rounding_order.push_back(general_fractionality_vars[
i].second);
601 for (
const int var_index : rounding_order) {
602 if (time_limit_->LimitReached())
return false;
604 const IntegerVariable var = integer_variables_[var_index];
608 const IntegerValue lb = integer_trail_->LowerBound(var);
609 const IntegerValue ub = integer_trail_->UpperBound(var);
611 integer_solution_[var_index] = lb.value();
615 const int64_t rounded_value =
617 const int64_t floor_value =
619 const int64_t ceil_value =
622 const bool floor_is_in_domain =
623 (domain.
Contains(floor_value) && lb.value() <= floor_value);
624 const bool ceil_is_in_domain =
625 (domain.
Contains(ceil_value) && ub.value() >= ceil_value);
626 if (domain.IsEmpty()) {
627 integer_solution_[var_index] = rounded_value;
628 sat_solver_->NotifyThatModelIsUnsat();
632 if (ceil_value < lb.value()) {
633 integer_solution_[var_index] = lb.value();
634 }
else if (floor_value > ub.value()) {
635 integer_solution_[var_index] = ub.value();
636 }
else if (ceil_is_in_domain && floor_is_in_domain) {
637 DCHECK(domain.
Contains(rounded_value));
638 integer_solution_[var_index] = rounded_value;
639 }
else if (ceil_is_in_domain) {
640 integer_solution_[var_index] = ceil_value;
641 }
else if (floor_is_in_domain) {
642 integer_solution_[var_index] = floor_value;
644 const std::pair<IntegerLiteral, IntegerLiteral> values_in_domain =
645 integer_encoder_->Canonicalize(
647 const int64_t lower_value = values_in_domain.first.bound.value();
648 const int64_t higher_value = -values_in_domain.second.bound.value();
649 const int64_t distance_from_lower_value =
650 std::abs(lower_value - rounded_value);
651 const int64_t distance_from_higher_value =
652 std::abs(higher_value - rounded_value);
654 integer_solution_[var_index] =
655 (distance_from_lower_value < distance_from_higher_value)
660 CHECK(domain.
Contains(integer_solution_[var_index]));
661 CHECK_GE(integer_solution_[var_index], lb);
662 CHECK_LE(integer_solution_[var_index], ub);
671 const IntegerValue value(integer_solution_[var_index]);
673 to_enqueue = integer_encoder_->GetOrCreateAssociatedLiteral(
675 }
else if (value == ub) {
676 to_enqueue = integer_encoder_->GetOrCreateAssociatedLiteral(
680 integer_encoder_->GetOrCreateLiteralAssociatedToEquality(var, value);
683 if (!sat_solver_->FinishPropagation())
return false;
684 sat_solver_->EnqueueDecisionAndBacktrackOnConflict(to_enqueue);
685 if (sat_solver_->ModelIsUnsat())
return false;
687 integer_solution_is_set_ =
true;
688 return sat_solver_->ResetToLevelZero();
691void FeasibilityPump::FillIntegerSolutionStats() {
693 integer_solution_objective_ = 0;
694 for (
const auto& term : integer_objective_) {
695 integer_solution_objective_ +=
696 integer_solution_[term.first.value()] * term.second.value();
699 integer_solution_is_feasible_ =
true;
700 num_infeasible_constraints_ = 0;
701 integer_solution_infeasibility_ = 0;
702 for (RowIndex
i(0);
i < integer_lp_.size(); ++
i) {
703 int64_t activity = 0;
704 for (
const auto& term : integer_lp_[
i].terms) {
706 CapProd(integer_solution_[term.first.value()], term.second.value());
707 if (prod <= std::numeric_limits<int64_t>::min() ||
708 prod >= std::numeric_limits<int64_t>::max()) {
712 activity =
CapAdd(activity, prod);
713 if (activity <= std::numeric_limits<int64_t>::min() ||
714 activity >= std::numeric_limits<int64_t>::max())
717 if (activity > integer_lp_[
i].ub || activity < integer_lp_[
i].lb) {
718 integer_solution_is_feasible_ =
false;
719 num_infeasible_constraints_++;
720 const int64_t ub_infeasibility =
721 activity > integer_lp_[
i].ub.value()
722 ? activity - integer_lp_[
i].ub.value()
724 const int64_t lb_infeasibility =
725 activity < integer_lp_[
i].lb.value()
726 ? integer_lp_[
i].lb.value() - activity
728 integer_solution_infeasibility_ =
729 std::max(integer_solution_infeasibility_,
730 std::max(ub_infeasibility, lb_infeasibility));
@ INTEGER
The variable must only take integer values.
const DenseRow & variable_lower_bounds() const
ColIndex GetSlackVariable(RowIndex row) const
const DenseRow & variable_upper_bounds() const
const SparseColumn & GetSparseColumn(ColIndex col) const
Fractional UnscaleVariableValue(ColIndex col, Fractional value) const
Transforms corresponding value from the scaled domain to the original one.
Fractional GetVariableValue(ColIndex col) const
int GetProtoVariableFromIntegerVariable(IntegerVariable var) const
FeasibilityPump(Model *model)
void SetObjectiveCoefficient(IntegerVariable ivar, IntegerValue coeff)
glop::RowIndex ConstraintIndex
int64_t GetIntegerSolutionValue(IntegerVariable variable) const
bool Solve()
Returns false if the model is proven to be infeasible.
void AddLinearConstraint(const LinearConstraint &ct)
Add a new linear constraint to this LP.
double GetLPSolutionValue(IntegerVariable variable) const
void AddSolution(const std::vector< double > &lp_solution)
constexpr double kInfinity
Infinity for type Fractional.
std::vector< ColIndex > ColIndexVector
Vector of row or column indices. Useful to list the non-zero positions.
constexpr double kInfinity
Infinity for type Fractional.
IntType IntTypeAbs(IntType t)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
IntegerVariable PositiveVariable(IntegerVariable i)
int64_t SafeDoubleToInt64(double value)
PositiveOnlyIndex GetPositiveOnlyIndex(IntegerVariable var)
bool VariableIsPositive(IntegerVariable i)
double ToDouble(IntegerValue value)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
bool Contains(int64_t value) const
Various inclusion tests on a domain.
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
std::unique_ptr< IntegerValue[]> coeffs
std::unique_ptr< IntegerVariable[]> vars
absl::Span< const IntegerVariable > VarsAsSpan() const