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"
40#include "ortools/sat/sat_parameters.pb.h"
56const double FeasibilityPump::kCpEpsilon = 1e-4;
59 : sat_parameters_(*(
model->GetOrCreate<SatParameters>())),
81 VLOG(1) <<
"Feasibility Pump Total number of simplex iterations: "
82 << total_num_simplex_iterations_;
87 for (
const IntegerVariable
var :
ct.VarsAsSpan()) {
91 integer_lp_.
push_back(LinearConstraintInternal());
92 LinearConstraintInternal& new_ct = integer_lp_.back();
95 CHECK_LE(
ct.lb,
ct.ub);
96 for (
int i = 0;
i <
ct.num_terms; ++
i) {
98 IntegerVariable
var =
ct.vars[
i];
99 IntegerValue coeff =
ct.coeffs[
i];
104 new_ct.terms.push_back({GetOrCreateMirrorVariable(
var), coeff});
107 std::sort(new_ct.terms.begin(), new_ct.terms.end());
111 IntegerValue coeff) {
112 objective_is_defined_ =
true;
113 const IntegerVariable pos_var =
115 if (ivar != pos_var) coeff = -coeff;
117 const auto it = mirror_lp_variable_.find(pos_var);
118 if (it == mirror_lp_variable_.end())
return;
119 const ColIndex
col = it->second;
120 integer_objective_.push_back({
col, coeff});
121 objective_infinity_norm_ =
122 std::max(objective_infinity_norm_,
IntTypeAbs(coeff));
125ColIndex FeasibilityPump::GetOrCreateMirrorVariable(
126 IntegerVariable positive_variable) {
129 const auto it = mirror_lp_variable_.find(positive_variable);
130 if (it == mirror_lp_variable_.end()) {
133 model_vars_size_ = std::max(model_vars_size_,
model_var + 1);
135 const ColIndex
col(integer_variables_.size());
136 mirror_lp_variable_[positive_variable] =
col;
137 integer_variables_.push_back(positive_variable);
138 var_is_binary_.push_back(
false);
139 lp_solution_.push_back(std::numeric_limits<double>::infinity());
140 integer_solution_.push_back(0);
147void FeasibilityPump::PrintStats() {
148 if (lp_solution_is_set_) {
149 VLOG(2) <<
"Fractionality: " << lp_solution_fractionality_;
151 VLOG(2) <<
"Fractionality: NA";
155 if (integer_solution_is_set_) {
156 VLOG(2) <<
"#Infeasible const: " << num_infeasible_constraints_;
157 VLOG(2) <<
"Infeasibility: " << integer_solution_infeasibility_;
159 VLOG(2) <<
"Infeasibility: NA";
165 InitializeWorkingLP();
167 UpdateBoundsOfLpVariables();
168 lp_solution_is_set_ =
false;
169 integer_solution_is_set_ =
false;
175 for (
const auto& term : integer_objective_) {
179 mixing_factor_ = 1.0;
180 for (
int i = 0;
i < max_fp_iterations_; ++
i) {
182 L1DistanceMinimize();
183 if (!SolveLp())
break;
184 if (lp_solution_is_integer_)
break;
188 if (integer_solution_is_feasible_) MaybePushToRepo();
198void FeasibilityPump::MaybePushToRepo() {
199 if (incomplete_solutions_ ==
nullptr)
return;
201 std::vector<double> lp_solution(model_vars_size_,
202 std::numeric_limits<double>::infinity());
204 if (lp_solution_is_integer_) {
206 for (
const IntegerVariable positive_var : integer_variables_) {
216 if (integer_solution_is_feasible_) {
218 for (
const IntegerVariable positive_var : integer_variables_) {
233void FeasibilityPump::InitializeWorkingLP() {
236 for (
int i = 0;
i < integer_variables_.size(); ++
i) {
243 for (
const LinearConstraintInternal&
ct : integer_lp_) {
246 for (
const auto& term :
ct.terms) {
252 for (
const auto& term : integer_objective_) {
256 const int num_vars = integer_variables_.size();
257 for (
int i = 0;
i < num_vars;
i++) {
258 const IntegerVariable cp_var = integer_variables_[
i];
264 objective_normalization_factor_ = 0.0;
269 if (!var_is_binary_[
col.value()]) {
270 integer_variables.push_back(
col);
275 objective_normalization_factor_ +=
279 objective_normalization_factor_ =
282 if (!integer_variables.empty()) {
284 norm_variables_.
assign(num_cols, ColIndex(-1));
285 norm_lhs_constraints_.
assign(num_cols, RowIndex(-1));
286 norm_rhs_constraints_.
assign(num_cols, RowIndex(-1));
303 for (
const ColIndex
col : integer_variables) {
305 norm_variables_[
col] = norm_variable;
308 norm_lhs_constraints_[
col] = row_a;
312 norm_rhs_constraints_[
col] = row_b;
318 scaler_.
Scale(&lp_data_);
323void FeasibilityPump::L1DistanceMinimize() {
324 std::vector<double> new_obj_coeffs(lp_data_.
num_variables().value(), 0.0);
329 for (ColIndex
col(0);
col < num_cols; ++
col) {
330 new_obj_coeffs[
col.value()] =
337 if (var_is_binary_[
col.value()]) {
340 (1 - mixing_factor_) * objective_normalization_factor_ *
341 (1 - 2 * integer_solution_[
col.value()]);
342 new_obj_coeffs[
col.value()] = objective_coefficient;
354 (1 - mixing_factor_) * objective_normalization_factor_;
355 new_obj_coeffs[norm_variables_[
col].value()] = objective_coefficient;
359 const ColIndex norm_lhs_slack_variable =
361 const double lhs_scaling_factor =
365 lhs_scaling_factor * integer_solution_[
col.value()]);
366 const ColIndex norm_rhs_slack_variable =
368 const double rhs_scaling_factor =
372 -rhs_scaling_factor * integer_solution_[
col.value()]);
379 mixing_factor_ *= 0.8;
382bool FeasibilityPump::SolveLp() {
383 const int num_vars = integer_variables_.size();
386 const auto status = simplex_.
Solve(lp_data_, time_limit_);
389 VLOG(1) <<
"The LP solver encountered an error: " <<
status.error_message();
402 lp_solution_fractionality_ = 0.0;
407 lp_solution_is_set_ =
true;
408 for (
int i = 0;
i < num_vars;
i++) {
409 const double value = GetVariableValueAtCpScale(ColIndex(
i));
411 lp_solution_fractionality_ = std::max(
412 lp_solution_fractionality_, std::abs(
value - std::round(
value)));
417 for (
const auto& term : integer_objective_) {
418 lp_objective_ += lp_solution_[term.first.value()] * term.second.value();
420 lp_solution_is_integer_ = lp_solution_fractionality_ < kCpEpsilon;
425void FeasibilityPump::UpdateBoundsOfLpVariables() {
426 const int num_vars = integer_variables_.size();
427 for (
int i = 0;
i < num_vars;
i++) {
428 const IntegerVariable cp_var = integer_variables_[
i];
437 return lp_solution_[mirror_lp_variable_.at(variable).value()];
440double FeasibilityPump::GetVariableValueAtCpScale(ColIndex
var) {
449 IntegerVariable variable)
const {
450 return integer_solution_[mirror_lp_variable_.at(variable).value()];
453bool FeasibilityPump::Round() {
454 bool rounding_successful =
true;
455 if (sat_parameters_.fp_rounding() == SatParameters::NEAREST_INTEGER) {
456 rounding_successful = NearestIntegerRounding();
457 }
else if (sat_parameters_.fp_rounding() == SatParameters::LOCK_BASED) {
458 rounding_successful = LockBasedRounding();
459 }
else if (sat_parameters_.fp_rounding() ==
460 SatParameters::ACTIVE_LOCK_BASED) {
461 rounding_successful = ActiveLockBasedRounding();
462 }
else if (sat_parameters_.fp_rounding() ==
463 SatParameters::PROPAGATION_ASSISTED) {
464 rounding_successful = PropagationRounding();
466 if (!rounding_successful)
return false;
467 FillIntegerSolutionStats();
471bool FeasibilityPump::NearestIntegerRounding() {
472 if (!lp_solution_is_set_)
return false;
473 for (
int i = 0;
i < lp_solution_.size(); ++
i) {
474 integer_solution_[
i] =
static_cast<int64_t
>(std::round(lp_solution_[
i]));
476 integer_solution_is_set_ =
true;
480bool FeasibilityPump::LockBasedRounding() {
481 if (!lp_solution_is_set_)
return false;
482 const int num_vars = integer_variables_.size();
486 if (var_up_locks_.empty()) {
487 var_up_locks_.resize(num_vars, 0);
488 var_down_locks_.resize(num_vars, 0);
489 for (
int i = 0;
i < num_vars; ++
i) {
492 const bool constraint_upper_bounded =
495 const bool constraint_lower_bounded =
498 if (entry.coefficient() > 0) {
499 var_up_locks_[
i] += constraint_upper_bounded;
500 var_down_locks_[
i] += constraint_lower_bounded;
502 var_up_locks_[
i] += constraint_lower_bounded;
503 var_down_locks_[
i] += constraint_upper_bounded;
509 for (
int i = 0;
i < lp_solution_.size(); ++
i) {
510 if (std::abs(lp_solution_[
i] - std::round(lp_solution_[
i])) < 0.1 ||
511 var_up_locks_[
i] == var_down_locks_[
i]) {
512 integer_solution_[
i] =
static_cast<int64_t
>(std::round(lp_solution_[
i]));
513 }
else if (var_up_locks_[
i] > var_down_locks_[
i]) {
514 integer_solution_[
i] =
static_cast<int64_t
>(std::floor(lp_solution_[
i]));
516 integer_solution_[
i] =
static_cast<int64_t
>(std::ceil(lp_solution_[
i]));
519 integer_solution_is_set_ =
true;
523bool FeasibilityPump::ActiveLockBasedRounding() {
524 if (!lp_solution_is_set_)
return false;
525 const int num_vars = integer_variables_.size();
530 for (
int i = 0;
i < num_vars; ++
i) {
531 if (std::abs(lp_solution_[
i] - std::round(lp_solution_[
i])) < 0.1) {
532 integer_solution_[
i] =
static_cast<int64_t
>(std::round(lp_solution_[
i]));
541 if (entry.coefficient() > 0) {
547 if (entry.coefficient() > 0) {
554 if (up_locks == down_locks) {
555 integer_solution_[
i] =
static_cast<int64_t
>(std::round(lp_solution_[
i]));
556 }
else if (up_locks > down_locks) {
557 integer_solution_[
i] =
static_cast<int64_t
>(std::floor(lp_solution_[
i]));
559 integer_solution_[
i] =
static_cast<int64_t
>(std::ceil(lp_solution_[
i]));
563 integer_solution_is_set_ =
true;
567bool FeasibilityPump::PropagationRounding() {
568 if (!lp_solution_is_set_)
return false;
572 std::vector<int> rounding_order;
574 std::vector<std::pair<double, int>> binary_fractionality_vars;
575 std::vector<std::pair<double, int>> general_fractionality_vars;
576 for (
int i = 0;
i < lp_solution_.size(); ++
i) {
577 const double fractionality =
578 std::abs(std::round(lp_solution_[
i]) - lp_solution_[
i]);
579 if (var_is_binary_[
i]) {
580 binary_fractionality_vars.push_back({fractionality,
i});
582 general_fractionality_vars.push_back({fractionality,
i});
585 std::sort(binary_fractionality_vars.begin(),
586 binary_fractionality_vars.end());
587 std::sort(general_fractionality_vars.begin(),
588 general_fractionality_vars.end());
590 for (
int i = 0;
i < binary_fractionality_vars.size(); ++
i) {
591 rounding_order.push_back(binary_fractionality_vars[
i].second);
593 for (
int i = 0;
i < general_fractionality_vars.size(); ++
i) {
594 rounding_order.push_back(general_fractionality_vars[
i].second);
598 for (
const int var_index : rounding_order) {
601 const IntegerVariable
var = integer_variables_[
var_index];
608 integer_solution_[
var_index] = lb.value();
612 const int64_t rounded_value =
613 static_cast<int64_t
>(std::round(lp_solution_[
var_index]));
614 const int64_t floor_value =
615 static_cast<int64_t
>(std::floor(lp_solution_[
var_index]));
616 const int64_t ceil_value =
617 static_cast<int64_t
>(std::ceil(lp_solution_[
var_index]));
619 const bool floor_is_in_domain =
620 (domain.Contains(floor_value) && lb.value() <= floor_value);
621 const bool ceil_is_in_domain =
622 (domain.Contains(ceil_value) && ub.value() >= ceil_value);
623 if (domain.IsEmpty()) {
624 integer_solution_[
var_index] = rounded_value;
629 if (ceil_value < lb.value()) {
630 integer_solution_[
var_index] = lb.value();
631 }
else if (floor_value > ub.value()) {
632 integer_solution_[
var_index] = ub.value();
633 }
else if (ceil_is_in_domain && floor_is_in_domain) {
634 DCHECK(domain.Contains(rounded_value));
635 integer_solution_[
var_index] = rounded_value;
636 }
else if (ceil_is_in_domain) {
637 integer_solution_[
var_index] = ceil_value;
638 }
else if (floor_is_in_domain) {
639 integer_solution_[
var_index] = floor_value;
641 const std::pair<IntegerLiteral, IntegerLiteral> values_in_domain =
644 const int64_t lower_value = values_in_domain.first.bound.value();
645 const int64_t higher_value = -values_in_domain.second.bound.value();
646 const int64_t distance_from_lower_value =
647 std::abs(lower_value - rounded_value);
648 const int64_t distance_from_higher_value =
649 std::abs(higher_value - rounded_value);
652 (distance_from_lower_value < distance_from_higher_value)
657 CHECK(domain.Contains(integer_solution_[
var_index]));
658 CHECK_GE(integer_solution_[
var_index], lb);
659 CHECK_LE(integer_solution_[
var_index], ub);
672 }
else if (
value == ub) {
684 integer_solution_is_set_ =
true;
688void FeasibilityPump::FillIntegerSolutionStats() {
690 integer_solution_objective_ = 0;
691 for (
const auto& term : integer_objective_) {
692 integer_solution_objective_ +=
693 integer_solution_[term.first.value()] * term.second.value();
696 integer_solution_is_feasible_ =
true;
697 num_infeasible_constraints_ = 0;
698 integer_solution_infeasibility_ = 0;
699 for (RowIndex
i(0);
i < integer_lp_.size(); ++
i) {
700 int64_t activity = 0;
701 for (
const auto& term : integer_lp_[
i].terms) {
703 CapProd(integer_solution_[term.first.value()], term.second.value());
704 if (prod <= std::numeric_limits<int64_t>::min() ||
705 prod >= std::numeric_limits<int64_t>::max()) {
709 activity =
CapAdd(activity, prod);
710 if (activity <= std::numeric_limits<int64_t>::min() ||
711 activity >= std::numeric_limits<int64_t>::max())
714 if (activity > integer_lp_[
i].ub || activity < integer_lp_[
i].lb) {
715 integer_solution_is_feasible_ =
false;
716 num_infeasible_constraints_++;
717 const int64_t ub_infeasibility =
718 activity > integer_lp_[
i].ub.value()
719 ? activity - integer_lp_[
i].ub.value()
721 const int64_t lb_infeasibility =
722 activity < integer_lp_[
i].lb.value()
723 ? integer_lp_[
i].lb.value() - activity
725 integer_solution_infeasibility_ =
726 std::max(integer_solution_infeasibility_,
727 std::max(ub_infeasibility, lb_infeasibility));
std::string GetDimensionString() const
A short string with the problem dimension.
const DenseRow & objective_coefficients() const
Returns the objective coefficients (or cost) of variables as a row vector.
void Clear()
Clears, i.e. reset the object to its initial value.
@ INTEGER
The variable must only take integer values.
Fractional GetObjectiveCoefficientForMinimizationVersion(ColIndex col) const
void SetObjectiveCoefficient(ColIndex col, Fractional value)
void AddSlackVariablesWhereNecessary(bool detect_integer_constraints)
const DenseRow & variable_lower_bounds() const
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
ColIndex GetSlackVariable(RowIndex row) const
const std::vector< ColIndex > & IntegerVariablesList() const
bool IsVariableBinary(ColIndex col) const
Returns whether the variable at column col must take binary values or not.
void SetVariableType(ColIndex col, VariableType type)
Set the type of the variable.
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
Defines the coefficient for col / row.
const DenseRow & variable_upper_bounds() const
RowIndex CreateNewConstraint()
const SparseColumn & GetSparseColumn(ColIndex col) const
ColIndex num_variables() const
Returns the number of variables.
ColIndex CreateNewVariable()
void Scale(LinearProgram *lp)
Scale the given LP.
Fractional VariableScalingFactor(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
ProblemStatus GetProblemStatus() const
ConstraintStatus GetConstraintStatus(RowIndex row) const
void ClearStateForNextSolve()
void SetParameters(const GlopParameters ¶meters)
Sets or gets the algorithm parameters to be used on the next Solve().
ABSL_MUST_USE_RESULT Status Solve(const LinearProgram &lp, TimeLimit *time_limit)
int64_t GetNumberOfIterations() const
void assign(IntType size, const T &v)
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
std::pair< IntegerLiteral, IntegerLiteral > Canonicalize(IntegerLiteral i_lit) const
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
Literal GetOrCreateLiteralAssociatedToEquality(IntegerVariable var, IntegerValue value)
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.
Status EnqueueDecisionAndBacktrackOnConflict(Literal true_literal, int *first_propagation_index=nullptr)
void NotifyThatModelIsUnsat()
bool ModelIsUnsat() const
ABSL_MUST_USE_RESULT bool ResetToLevelZero()
ABSL_MUST_USE_RESULT bool FinishPropagation()
void AddSolution(const std::vector< double > &lp_solution)
void push_back(const value_type &val)
constexpr double kInfinity
Infinity for type Fractional.
std::vector< ColIndex > ColIndexVector
Vector of row or column indices. Useful to list the non-zero positions.
IntType IntTypeAbs(IntType t)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
IntegerVariable PositiveVariable(IntegerVariable i)
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)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)