739 lines
28 KiB
C++
739 lines
28 KiB
C++
// Copyright 2010-2025 Google LLC
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "ortools/sat/feasibility_pump.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/container/flat_hash_map.h"
|
|
#include "absl/log/check.h"
|
|
#include "absl/meta/type_traits.h"
|
|
#include "ortools/base/logging.h"
|
|
#include "ortools/base/strong_vector.h"
|
|
#include "ortools/glop/parameters.pb.h"
|
|
#include "ortools/glop/revised_simplex.h"
|
|
#include "ortools/glop/status.h"
|
|
#include "ortools/lp_data/lp_data.h"
|
|
#include "ortools/lp_data/lp_data_utils.h"
|
|
#include "ortools/lp_data/lp_types.h"
|
|
#include "ortools/lp_data/sparse_column.h"
|
|
#include "ortools/sat/cp_model_mapping.h"
|
|
#include "ortools/sat/integer.h"
|
|
#include "ortools/sat/integer_base.h"
|
|
#include "ortools/sat/linear_constraint.h"
|
|
#include "ortools/sat/model.h"
|
|
#include "ortools/sat/sat_base.h"
|
|
#include "ortools/sat/sat_parameters.pb.h"
|
|
#include "ortools/sat/sat_solver.h"
|
|
#include "ortools/sat/synchronization.h"
|
|
#include "ortools/sat/util.h"
|
|
#include "ortools/util/saturated_arithmetic.h"
|
|
#include "ortools/util/sorted_interval_list.h"
|
|
#include "ortools/util/strong_integers.h"
|
|
#include "ortools/util/time_limit.h"
|
|
|
|
namespace operations_research {
|
|
namespace sat {
|
|
|
|
using glop::ColIndex;
|
|
using glop::ConstraintStatus;
|
|
using glop::Fractional;
|
|
using glop::RowIndex;
|
|
|
|
const double FeasibilityPump::kCpEpsilon = 1e-4;
|
|
|
|
FeasibilityPump::FeasibilityPump(Model* model)
|
|
: sat_parameters_(*(model->GetOrCreate<SatParameters>())),
|
|
time_limit_(model->GetOrCreate<TimeLimit>()),
|
|
integer_trail_(model->GetOrCreate<IntegerTrail>()),
|
|
trail_(model->GetOrCreate<Trail>()),
|
|
integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
|
|
incomplete_solutions_(model->Mutable<SharedIncompleteSolutionManager>()),
|
|
sat_solver_(model->GetOrCreate<SatSolver>()),
|
|
domains_(model->GetOrCreate<IntegerDomains>()),
|
|
mapping_(model->Get<CpModelMapping>()) {
|
|
// Tweak the default parameters to make the solve incremental.
|
|
glop::GlopParameters parameters;
|
|
// Note(user): Primal simplex does better here since we have a limit on
|
|
// simplex iterations. So dual simplex sometimes fails to find a LP feasible
|
|
// solution.
|
|
parameters.set_use_dual_simplex(false);
|
|
parameters.set_max_number_of_iterations(2000);
|
|
simplex_.SetParameters(parameters);
|
|
lp_data_.Clear();
|
|
integer_lp_.clear();
|
|
}
|
|
|
|
FeasibilityPump::~FeasibilityPump() {
|
|
VLOG(1) << "Feasibility Pump Total number of simplex iterations: "
|
|
<< total_num_simplex_iterations_;
|
|
}
|
|
|
|
void FeasibilityPump::AddLinearConstraint(const LinearConstraint& ct) {
|
|
// We still create the mirror variable right away though.
|
|
for (const IntegerVariable var : ct.VarsAsSpan()) {
|
|
GetOrCreateMirrorVariable(PositiveVariable(var));
|
|
}
|
|
|
|
integer_lp_.push_back(LinearConstraintInternal());
|
|
LinearConstraintInternal& new_ct = integer_lp_.back();
|
|
new_ct.lb = ct.lb;
|
|
new_ct.ub = ct.ub;
|
|
CHECK_LE(ct.lb, ct.ub);
|
|
for (int i = 0; i < ct.num_terms; ++i) {
|
|
// We only use positive variable inside this class.
|
|
IntegerVariable var = ct.vars[i];
|
|
IntegerValue coeff = ct.coeffs[i];
|
|
if (!VariableIsPositive(var)) {
|
|
var = NegationOf(var);
|
|
coeff = -coeff;
|
|
}
|
|
new_ct.terms.push_back({GetOrCreateMirrorVariable(var), coeff});
|
|
}
|
|
// Important to keep lp_data_ "clean".
|
|
std::sort(new_ct.terms.begin(), new_ct.terms.end());
|
|
}
|
|
|
|
void FeasibilityPump::SetObjectiveCoefficient(IntegerVariable ivar,
|
|
IntegerValue coeff) {
|
|
objective_is_defined_ = true;
|
|
const IntegerVariable pos_var =
|
|
VariableIsPositive(ivar) ? ivar : NegationOf(ivar);
|
|
if (ivar != pos_var) coeff = -coeff;
|
|
|
|
const auto it = mirror_lp_variable_.find(pos_var);
|
|
if (it == mirror_lp_variable_.end()) return;
|
|
const ColIndex col = it->second;
|
|
integer_objective_.push_back({col, coeff});
|
|
objective_infinity_norm_ =
|
|
std::max(objective_infinity_norm_, IntTypeAbs(coeff));
|
|
}
|
|
|
|
ColIndex FeasibilityPump::GetOrCreateMirrorVariable(
|
|
IntegerVariable positive_variable) {
|
|
DCHECK(VariableIsPositive(positive_variable));
|
|
|
|
const auto it = mirror_lp_variable_.find(positive_variable);
|
|
if (it == mirror_lp_variable_.end()) {
|
|
const int model_var =
|
|
mapping_->GetProtoVariableFromIntegerVariable(positive_variable);
|
|
model_vars_size_ = std::max(model_vars_size_, model_var + 1);
|
|
|
|
const ColIndex col(integer_variables_.size());
|
|
mirror_lp_variable_[positive_variable] = col;
|
|
integer_variables_.push_back(positive_variable);
|
|
var_is_binary_.push_back(false);
|
|
lp_solution_.push_back(std::numeric_limits<double>::infinity());
|
|
integer_solution_.push_back(0);
|
|
|
|
return col;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
void FeasibilityPump::PrintStats() {
|
|
if (lp_solution_is_set_) {
|
|
VLOG(2) << "Fractionality: " << lp_solution_fractionality_;
|
|
} else {
|
|
VLOG(2) << "Fractionality: NA";
|
|
VLOG(2) << "simplex status: " << simplex_.GetProblemStatus();
|
|
}
|
|
|
|
if (integer_solution_is_set_) {
|
|
VLOG(2) << "#Infeasible const: " << num_infeasible_constraints_;
|
|
VLOG(2) << "Infeasibility: " << integer_solution_infeasibility_;
|
|
} else {
|
|
VLOG(2) << "Infeasibility: NA";
|
|
}
|
|
}
|
|
|
|
bool FeasibilityPump::Solve() {
|
|
if (lp_data_.num_variables() == 0) {
|
|
InitializeWorkingLP();
|
|
}
|
|
UpdateBoundsOfLpVariables();
|
|
lp_solution_is_set_ = false;
|
|
integer_solution_is_set_ = false;
|
|
|
|
// Restore the original objective
|
|
for (ColIndex col(0); col < lp_data_.num_variables(); ++col) {
|
|
lp_data_.SetObjectiveCoefficient(col, 0.0);
|
|
}
|
|
for (const auto& term : integer_objective_) {
|
|
lp_data_.SetObjectiveCoefficient(term.first, ToDouble(term.second));
|
|
}
|
|
|
|
mixing_factor_ = 1.0;
|
|
for (int i = 0; i < max_fp_iterations_; ++i) {
|
|
if (time_limit_->LimitReached()) break;
|
|
L1DistanceMinimize();
|
|
if (!SolveLp()) break;
|
|
if (lp_solution_is_integer_) break;
|
|
if (!Round()) break;
|
|
// We don't end this loop if the integer solutions is feasible in hope to
|
|
// get better solution.
|
|
if (integer_solution_is_feasible_) MaybePushToRepo();
|
|
}
|
|
|
|
if (sat_solver_->ModelIsUnsat()) return false;
|
|
|
|
PrintStats();
|
|
MaybePushToRepo();
|
|
return true;
|
|
}
|
|
|
|
void FeasibilityPump::MaybePushToRepo() {
|
|
if (incomplete_solutions_ == nullptr) return;
|
|
const double kInfinity = std::numeric_limits<double>::infinity();
|
|
|
|
// TODO(user): Consider adding solutions that have low fractionality.
|
|
if (lp_solution_is_integer_) {
|
|
// Fill the solution using LP solution values.
|
|
tmp_solution_.assign(model_vars_size_, kInfinity);
|
|
for (const IntegerVariable positive_var : integer_variables_) {
|
|
const int model_var =
|
|
mapping_->GetProtoVariableFromIntegerVariable(positive_var);
|
|
if (model_var >= 0 && model_var < model_vars_size_) {
|
|
tmp_solution_[model_var] = GetLPSolutionValue(positive_var);
|
|
}
|
|
}
|
|
incomplete_solutions_->AddSolution(tmp_solution_);
|
|
}
|
|
|
|
if (integer_solution_is_feasible_) {
|
|
// Fill the solution using Integer solution values.
|
|
tmp_solution_.assign(model_vars_size_, kInfinity);
|
|
for (const IntegerVariable positive_var : integer_variables_) {
|
|
const int model_var =
|
|
mapping_->GetProtoVariableFromIntegerVariable(positive_var);
|
|
if (model_var >= 0 && model_var < model_vars_size_) {
|
|
tmp_solution_[model_var] = GetIntegerSolutionValue(positive_var);
|
|
}
|
|
}
|
|
incomplete_solutions_->AddSolution(tmp_solution_);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// -------------------LPSolving------------------------------------
|
|
// ----------------------------------------------------------------
|
|
|
|
void FeasibilityPump::InitializeWorkingLP() {
|
|
lp_data_.Clear();
|
|
// Create variables.
|
|
for (int i = 0; i < integer_variables_.size(); ++i) {
|
|
CHECK_EQ(ColIndex(i), lp_data_.CreateNewVariable());
|
|
lp_data_.SetVariableType(ColIndex(i),
|
|
glop::LinearProgram::VariableType::INTEGER);
|
|
}
|
|
|
|
// Add constraints.
|
|
for (const LinearConstraintInternal& ct : integer_lp_) {
|
|
const ConstraintIndex row = lp_data_.CreateNewConstraint();
|
|
lp_data_.SetConstraintBounds(row, ToDouble(ct.lb), ToDouble(ct.ub));
|
|
for (const auto& term : ct.terms) {
|
|
lp_data_.SetCoefficient(row, term.first, ToDouble(term.second));
|
|
}
|
|
}
|
|
|
|
// Add objective.
|
|
for (const auto& term : integer_objective_) {
|
|
lp_data_.SetObjectiveCoefficient(term.first, ToDouble(term.second));
|
|
}
|
|
|
|
const int num_vars = integer_variables_.size();
|
|
for (int i = 0; i < num_vars; i++) {
|
|
const IntegerVariable cp_var = integer_variables_[i];
|
|
const double lb = ToDouble(integer_trail_->LevelZeroLowerBound(cp_var));
|
|
const double ub = ToDouble(integer_trail_->LevelZeroUpperBound(cp_var));
|
|
lp_data_.SetVariableBounds(ColIndex(i), lb, ub);
|
|
}
|
|
|
|
objective_normalization_factor_ = 0.0;
|
|
glop::ColIndexVector integer_variables;
|
|
const ColIndex num_cols = lp_data_.num_variables();
|
|
for (ColIndex col : lp_data_.IntegerVariablesList()) {
|
|
var_is_binary_[col.value()] = lp_data_.IsVariableBinary(col);
|
|
if (!var_is_binary_[col.value()]) {
|
|
integer_variables.push_back(col);
|
|
}
|
|
|
|
// The aim of this normalization value is to compute a coefficient of the
|
|
// d_i variables that should be minimized.
|
|
objective_normalization_factor_ +=
|
|
std::abs(lp_data_.GetObjectiveCoefficientForMinimizationVersion(col));
|
|
}
|
|
CHECK_GT(lp_data_.IntegerVariablesList().size(), 0);
|
|
objective_normalization_factor_ =
|
|
objective_normalization_factor_ / lp_data_.IntegerVariablesList().size();
|
|
|
|
if (!integer_variables.empty()) {
|
|
// Update the LpProblem with norm variables and constraints.
|
|
norm_variables_.assign(num_cols, ColIndex(-1));
|
|
norm_lhs_constraints_.assign(num_cols, RowIndex(-1));
|
|
norm_rhs_constraints_.assign(num_cols, RowIndex(-1));
|
|
// For each integer non-binary variable x_i we introduce one new variable
|
|
// d_i subject to two new constraints:
|
|
// d_i - x_i >= -round(x'_i)
|
|
// d_i + x_i >= +round(x'_i)
|
|
// That's round(x'_i) - d_i <= x_i <= round(x'_i) + d_i, where d_i is an
|
|
// unbounded non-negative, and x'_i is the value of variable i from the
|
|
// previous solution obtained during the projection step. Consequently
|
|
// coefficients of the constraints are set here, but bounds of the
|
|
// constraints are updated at each iteration of the feasibility pump. Also
|
|
// coefficients of the objective are set here: x_i's are not present in the
|
|
// objective (i.e., coefficients set to 0.0), and d_i's are present in the
|
|
// objective with coefficients set to 1.0.
|
|
// Note that the treatment of integer non-binary variables is different
|
|
// from the treatment of binary variables. Binary variables do not impose
|
|
// any extra variables, nor extra constraints, but their objective
|
|
// coefficients are changed in the linear projection steps.
|
|
for (const ColIndex col : integer_variables) {
|
|
const ColIndex norm_variable = lp_data_.CreateNewVariable();
|
|
norm_variables_[col] = norm_variable;
|
|
lp_data_.SetVariableBounds(norm_variable, 0.0, glop::kInfinity);
|
|
const RowIndex row_a = lp_data_.CreateNewConstraint();
|
|
norm_lhs_constraints_[col] = row_a;
|
|
lp_data_.SetCoefficient(row_a, norm_variable, 1.0);
|
|
lp_data_.SetCoefficient(row_a, col, -1.0);
|
|
const RowIndex row_b = lp_data_.CreateNewConstraint();
|
|
norm_rhs_constraints_[col] = row_b;
|
|
lp_data_.SetCoefficient(row_b, norm_variable, 1.0);
|
|
lp_data_.SetCoefficient(row_b, col, 1.0);
|
|
}
|
|
}
|
|
|
|
scaler_.Scale(&lp_data_);
|
|
lp_data_.AddSlackVariablesWhereNecessary(
|
|
/*detect_integer_constraints=*/false);
|
|
}
|
|
|
|
void FeasibilityPump::L1DistanceMinimize() {
|
|
std::vector<double> new_obj_coeffs(lp_data_.num_variables().value(), 0.0);
|
|
|
|
// Set the original subobjective. The coefficients are scaled by mixing factor
|
|
// and the offset remains at 0 (because it does not affect the solution).
|
|
const ColIndex num_cols(lp_data_.objective_coefficients().size());
|
|
for (ColIndex col(0); col < num_cols; ++col) {
|
|
new_obj_coeffs[col.value()] =
|
|
mixing_factor_ * lp_data_.objective_coefficients()[col];
|
|
}
|
|
|
|
// Set the norm subobjective. The coefficients are scaled by 1 - mixing factor
|
|
// and the offset remains at 0 (because it does not affect the solution).
|
|
for (const ColIndex col : lp_data_.IntegerVariablesList()) {
|
|
if (var_is_binary_[col.value()]) {
|
|
const Fractional objective_coefficient =
|
|
mixing_factor_ * lp_data_.objective_coefficients()[col] +
|
|
(1 - mixing_factor_) * objective_normalization_factor_ *
|
|
(1 - 2 * integer_solution_[col.value()]);
|
|
new_obj_coeffs[col.value()] = objective_coefficient;
|
|
} else { // The variable is integer.
|
|
// Update the bounds of the constraints added in
|
|
// InitializeIntegerVariables() (see there for more details):
|
|
// d_i - x_i >= -round(x'_i)
|
|
// d_i + x_i >= +round(x'_i)
|
|
|
|
// TODO(user): We change both the objective and the bounds, thus
|
|
// breaking the incrementality. Handle integer variables differently,
|
|
// e.g., intensify rounding, or use soft fixing from: Fischetti, Lodi,
|
|
// "Local Branching", Math Program Ser B 98:23-47 (2003).
|
|
const Fractional objective_coefficient =
|
|
(1 - mixing_factor_) * objective_normalization_factor_;
|
|
new_obj_coeffs[norm_variables_[col].value()] = objective_coefficient;
|
|
// At this point, constraint bounds have already been transformed into
|
|
// bounds of slack variables. Instead of updating the constraints, we need
|
|
// to update the slack variables corresponding to them.
|
|
const ColIndex norm_lhs_slack_variable =
|
|
lp_data_.GetSlackVariable(norm_lhs_constraints_[col]);
|
|
const double lhs_scaling_factor =
|
|
scaler_.VariableScalingFactorWithSlack(norm_lhs_slack_variable);
|
|
lp_data_.SetVariableBounds(
|
|
norm_lhs_slack_variable, -glop::kInfinity,
|
|
lhs_scaling_factor * integer_solution_[col.value()]);
|
|
const ColIndex norm_rhs_slack_variable =
|
|
lp_data_.GetSlackVariable(norm_rhs_constraints_[col]);
|
|
const double rhs_scaling_factor =
|
|
scaler_.VariableScalingFactorWithSlack(norm_rhs_slack_variable);
|
|
lp_data_.SetVariableBounds(
|
|
norm_rhs_slack_variable, -glop::kInfinity,
|
|
-rhs_scaling_factor * integer_solution_[col.value()]);
|
|
}
|
|
}
|
|
for (ColIndex col(0); col < lp_data_.num_variables(); ++col) {
|
|
lp_data_.SetObjectiveCoefficient(col, new_obj_coeffs[col.value()]);
|
|
}
|
|
// TODO(user): Tune this or expose as parameter.
|
|
mixing_factor_ *= 0.8;
|
|
}
|
|
|
|
bool FeasibilityPump::SolveLp() {
|
|
const int num_vars = integer_variables_.size();
|
|
VLOG(3) << "LP relaxation: " << lp_data_.GetDimensionString() << ".";
|
|
|
|
const auto status = simplex_.Solve(lp_data_, time_limit_);
|
|
total_num_simplex_iterations_ += simplex_.GetNumberOfIterations();
|
|
if (!status.ok()) {
|
|
VLOG(1) << "The LP solver encountered an error: " << status.error_message();
|
|
simplex_.ClearStateForNextSolve();
|
|
return false;
|
|
}
|
|
|
|
// TODO(user): This shouldn't really happen except if the problem is UNSAT.
|
|
// But we can't just rely on a potentially imprecise LP to close the problem.
|
|
// The rest of the solver should do that with exact precision.
|
|
VLOG(3) << "simplex status: " << simplex_.GetProblemStatus();
|
|
if (simplex_.GetProblemStatus() == glop::ProblemStatus::PRIMAL_INFEASIBLE) {
|
|
return false;
|
|
}
|
|
|
|
lp_solution_fractionality_ = 0.0;
|
|
if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL ||
|
|
simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_FEASIBLE ||
|
|
simplex_.GetProblemStatus() == glop::ProblemStatus::PRIMAL_FEASIBLE ||
|
|
simplex_.GetProblemStatus() == glop::ProblemStatus::IMPRECISE) {
|
|
lp_solution_is_set_ = true;
|
|
for (int i = 0; i < num_vars; i++) {
|
|
const double value = GetVariableValueAtCpScale(ColIndex(i));
|
|
lp_solution_[i] = value;
|
|
lp_solution_fractionality_ = std::max(
|
|
lp_solution_fractionality_, std::abs(value - std::round(value)));
|
|
}
|
|
|
|
// Compute the objective value.
|
|
lp_objective_ = 0;
|
|
for (const auto& term : integer_objective_) {
|
|
lp_objective_ += lp_solution_[term.first.value()] * term.second.value();
|
|
}
|
|
lp_solution_is_integer_ = lp_solution_fractionality_ < kCpEpsilon;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void FeasibilityPump::UpdateBoundsOfLpVariables() {
|
|
const int num_vars = integer_variables_.size();
|
|
for (int i = 0; i < num_vars; i++) {
|
|
const IntegerVariable cp_var = integer_variables_[i];
|
|
const double lb = ToDouble(integer_trail_->LevelZeroLowerBound(cp_var));
|
|
const double ub = ToDouble(integer_trail_->LevelZeroUpperBound(cp_var));
|
|
const double factor = scaler_.VariableScalingFactor(ColIndex(i));
|
|
lp_data_.SetVariableBounds(ColIndex(i), lb * factor, ub * factor);
|
|
}
|
|
}
|
|
|
|
double FeasibilityPump::GetLPSolutionValue(IntegerVariable variable) const {
|
|
return lp_solution_[mirror_lp_variable_.at(variable).value()];
|
|
}
|
|
|
|
double FeasibilityPump::GetVariableValueAtCpScale(ColIndex var) {
|
|
return scaler_.UnscaleVariableValue(var, simplex_.GetVariableValue(var));
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// -------------------Rounding-------------------------------------
|
|
// ----------------------------------------------------------------
|
|
|
|
int64_t FeasibilityPump::GetIntegerSolutionValue(
|
|
IntegerVariable variable) const {
|
|
return integer_solution_[mirror_lp_variable_.at(variable).value()];
|
|
}
|
|
|
|
bool FeasibilityPump::Round() {
|
|
bool rounding_successful = true;
|
|
if (sat_parameters_.fp_rounding() == SatParameters::NEAREST_INTEGER) {
|
|
rounding_successful = NearestIntegerRounding();
|
|
} else if (sat_parameters_.fp_rounding() == SatParameters::LOCK_BASED) {
|
|
rounding_successful = LockBasedRounding();
|
|
} else if (sat_parameters_.fp_rounding() ==
|
|
SatParameters::ACTIVE_LOCK_BASED) {
|
|
rounding_successful = ActiveLockBasedRounding();
|
|
} else if (sat_parameters_.fp_rounding() ==
|
|
SatParameters::PROPAGATION_ASSISTED) {
|
|
rounding_successful = PropagationRounding();
|
|
}
|
|
if (!rounding_successful) return false;
|
|
FillIntegerSolutionStats();
|
|
return true;
|
|
}
|
|
|
|
bool FeasibilityPump::NearestIntegerRounding() {
|
|
if (!lp_solution_is_set_) return false;
|
|
for (int i = 0; i < lp_solution_.size(); ++i) {
|
|
integer_solution_[i] = static_cast<int64_t>(std::round(lp_solution_[i]));
|
|
}
|
|
integer_solution_is_set_ = true;
|
|
return true;
|
|
}
|
|
|
|
bool FeasibilityPump::LockBasedRounding() {
|
|
if (!lp_solution_is_set_) return false;
|
|
const int num_vars = integer_variables_.size();
|
|
|
|
// We compute the number of locks based on variable coefficient in constraints
|
|
// and constraint bounds. This doesn't change over time so we cache it.
|
|
if (var_up_locks_.empty()) {
|
|
var_up_locks_.resize(num_vars, 0);
|
|
var_down_locks_.resize(num_vars, 0);
|
|
for (int i = 0; i < num_vars; ++i) {
|
|
for (const auto entry : lp_data_.GetSparseColumn(ColIndex(i))) {
|
|
ColIndex slack = lp_data_.GetSlackVariable(entry.row());
|
|
const bool constraint_upper_bounded =
|
|
lp_data_.variable_lower_bounds()[slack] > -glop::kInfinity;
|
|
|
|
const bool constraint_lower_bounded =
|
|
lp_data_.variable_upper_bounds()[slack] < glop::kInfinity;
|
|
|
|
if (entry.coefficient() > 0) {
|
|
var_up_locks_[i] += constraint_upper_bounded;
|
|
var_down_locks_[i] += constraint_lower_bounded;
|
|
} else {
|
|
var_up_locks_[i] += constraint_lower_bounded;
|
|
var_down_locks_[i] += constraint_upper_bounded;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < lp_solution_.size(); ++i) {
|
|
if (std::abs(lp_solution_[i] - std::round(lp_solution_[i])) < 0.1 ||
|
|
var_up_locks_[i] == var_down_locks_[i]) {
|
|
integer_solution_[i] = static_cast<int64_t>(std::round(lp_solution_[i]));
|
|
} else if (var_up_locks_[i] > var_down_locks_[i]) {
|
|
integer_solution_[i] = static_cast<int64_t>(std::floor(lp_solution_[i]));
|
|
} else {
|
|
integer_solution_[i] = static_cast<int64_t>(std::ceil(lp_solution_[i]));
|
|
}
|
|
}
|
|
integer_solution_is_set_ = true;
|
|
return true;
|
|
}
|
|
|
|
bool FeasibilityPump::ActiveLockBasedRounding() {
|
|
if (!lp_solution_is_set_) return false;
|
|
const int num_vars = integer_variables_.size();
|
|
|
|
// We compute the number of locks based on variable coefficient in constraints
|
|
// and constraint bounds of active constraints. We consider the bound of the
|
|
// constraint that is tight for the current lp solution.
|
|
for (int i = 0; i < num_vars; ++i) {
|
|
if (std::abs(lp_solution_[i] - std::round(lp_solution_[i])) < 0.1) {
|
|
integer_solution_[i] = static_cast<int64_t>(std::round(lp_solution_[i]));
|
|
}
|
|
|
|
int up_locks = 0;
|
|
int down_locks = 0;
|
|
for (const auto entry : lp_data_.GetSparseColumn(ColIndex(i))) {
|
|
const ConstraintStatus row_status =
|
|
simplex_.GetConstraintStatus(entry.row());
|
|
if (row_status == ConstraintStatus::AT_LOWER_BOUND) {
|
|
if (entry.coefficient() > 0) {
|
|
down_locks++;
|
|
} else {
|
|
up_locks++;
|
|
}
|
|
} else if (row_status == ConstraintStatus::AT_UPPER_BOUND) {
|
|
if (entry.coefficient() > 0) {
|
|
up_locks++;
|
|
} else {
|
|
down_locks++;
|
|
}
|
|
}
|
|
}
|
|
if (up_locks == down_locks) {
|
|
integer_solution_[i] = static_cast<int64_t>(std::round(lp_solution_[i]));
|
|
} else if (up_locks > down_locks) {
|
|
integer_solution_[i] = static_cast<int64_t>(std::floor(lp_solution_[i]));
|
|
} else {
|
|
integer_solution_[i] = static_cast<int64_t>(std::ceil(lp_solution_[i]));
|
|
}
|
|
}
|
|
|
|
integer_solution_is_set_ = true;
|
|
return true;
|
|
}
|
|
|
|
bool FeasibilityPump::PropagationRounding() {
|
|
if (!lp_solution_is_set_) return false;
|
|
if (!sat_solver_->ResetToLevelZero()) return false;
|
|
|
|
// Compute an order in which we will fix variables and do the propagation.
|
|
std::vector<int> rounding_order;
|
|
{
|
|
std::vector<std::pair<double, int>> binary_fractionality_vars;
|
|
std::vector<std::pair<double, int>> general_fractionality_vars;
|
|
for (int i = 0; i < lp_solution_.size(); ++i) {
|
|
const double fractionality =
|
|
std::abs(std::round(lp_solution_[i]) - lp_solution_[i]);
|
|
if (var_is_binary_[i]) {
|
|
binary_fractionality_vars.push_back({fractionality, i});
|
|
} else {
|
|
general_fractionality_vars.push_back({fractionality, i});
|
|
}
|
|
}
|
|
std::sort(binary_fractionality_vars.begin(),
|
|
binary_fractionality_vars.end());
|
|
std::sort(general_fractionality_vars.begin(),
|
|
general_fractionality_vars.end());
|
|
|
|
for (int i = 0; i < binary_fractionality_vars.size(); ++i) {
|
|
rounding_order.push_back(binary_fractionality_vars[i].second);
|
|
}
|
|
for (int i = 0; i < general_fractionality_vars.size(); ++i) {
|
|
rounding_order.push_back(general_fractionality_vars[i].second);
|
|
}
|
|
}
|
|
|
|
for (const int var_index : rounding_order) {
|
|
if (time_limit_->LimitReached()) return false;
|
|
// Get the bounds of the variable.
|
|
const IntegerVariable var = integer_variables_[var_index];
|
|
CHECK(VariableIsPositive(var));
|
|
const Domain& domain = (*domains_)[GetPositiveOnlyIndex(var)];
|
|
|
|
const IntegerValue lb = integer_trail_->LowerBound(var);
|
|
const IntegerValue ub = integer_trail_->UpperBound(var);
|
|
if (lb == ub) {
|
|
integer_solution_[var_index] = lb.value();
|
|
continue;
|
|
}
|
|
|
|
const int64_t rounded_value =
|
|
SafeDoubleToInt64(std::round(lp_solution_[var_index]));
|
|
const int64_t floor_value =
|
|
SafeDoubleToInt64(std::floor(lp_solution_[var_index]));
|
|
const int64_t ceil_value =
|
|
SafeDoubleToInt64(std::ceil(lp_solution_[var_index]));
|
|
|
|
const bool floor_is_in_domain =
|
|
(domain.Contains(floor_value) && lb.value() <= floor_value);
|
|
const bool ceil_is_in_domain =
|
|
(domain.Contains(ceil_value) && ub.value() >= ceil_value);
|
|
if (domain.IsEmpty()) {
|
|
integer_solution_[var_index] = rounded_value;
|
|
sat_solver_->NotifyThatModelIsUnsat();
|
|
return false;
|
|
}
|
|
|
|
if (ceil_value < lb.value()) {
|
|
integer_solution_[var_index] = lb.value();
|
|
} else if (floor_value > ub.value()) {
|
|
integer_solution_[var_index] = ub.value();
|
|
} else if (ceil_is_in_domain && floor_is_in_domain) {
|
|
DCHECK(domain.Contains(rounded_value));
|
|
integer_solution_[var_index] = rounded_value;
|
|
} else if (ceil_is_in_domain) {
|
|
integer_solution_[var_index] = ceil_value;
|
|
} else if (floor_is_in_domain) {
|
|
integer_solution_[var_index] = floor_value;
|
|
} else {
|
|
const std::pair<IntegerLiteral, IntegerLiteral> values_in_domain =
|
|
integer_encoder_->Canonicalize(
|
|
IntegerLiteral::GreaterOrEqual(var, IntegerValue(rounded_value)));
|
|
const int64_t lower_value = values_in_domain.first.bound.value();
|
|
const int64_t higher_value = -values_in_domain.second.bound.value();
|
|
const int64_t distance_from_lower_value =
|
|
std::abs(lower_value - rounded_value);
|
|
const int64_t distance_from_higher_value =
|
|
std::abs(higher_value - rounded_value);
|
|
|
|
integer_solution_[var_index] =
|
|
(distance_from_lower_value < distance_from_higher_value)
|
|
? lower_value
|
|
: higher_value;
|
|
}
|
|
|
|
CHECK(domain.Contains(integer_solution_[var_index]));
|
|
CHECK_GE(integer_solution_[var_index], lb);
|
|
CHECK_LE(integer_solution_[var_index], ub);
|
|
|
|
// Propagate the value.
|
|
//
|
|
// When we want to fix the variable at its lb or ub, we do not create an
|
|
// equality literal to minimize the number of new literal we create. This
|
|
// is because creating an "== value" literal will implicitly also create
|
|
// a ">= value" and a "<= value" literals.
|
|
Literal to_enqueue;
|
|
const IntegerValue value(integer_solution_[var_index]);
|
|
if (value == lb) {
|
|
to_enqueue = integer_encoder_->GetOrCreateAssociatedLiteral(
|
|
IntegerLiteral::LowerOrEqual(var, value));
|
|
} else if (value == ub) {
|
|
to_enqueue = integer_encoder_->GetOrCreateAssociatedLiteral(
|
|
IntegerLiteral::GreaterOrEqual(var, value));
|
|
} else {
|
|
to_enqueue =
|
|
integer_encoder_->GetOrCreateLiteralAssociatedToEquality(var, value);
|
|
}
|
|
|
|
if (!sat_solver_->FinishPropagation()) return false;
|
|
const SatSolver::Status decision_status =
|
|
sat_solver_->EnqueueDecisionAndBacktrackOnConflict(to_enqueue);
|
|
if (decision_status != SatSolver::Status::FEASIBLE) return false;
|
|
if (sat_solver_->ModelIsUnsat()) return false;
|
|
}
|
|
integer_solution_is_set_ = true;
|
|
return sat_solver_->ResetToLevelZero();
|
|
}
|
|
|
|
void FeasibilityPump::FillIntegerSolutionStats() {
|
|
// Compute the objective value.
|
|
integer_solution_objective_ = 0;
|
|
for (const auto& term : integer_objective_) {
|
|
integer_solution_objective_ +=
|
|
integer_solution_[term.first.value()] * term.second.value();
|
|
}
|
|
|
|
integer_solution_is_feasible_ = true;
|
|
num_infeasible_constraints_ = 0;
|
|
integer_solution_infeasibility_ = 0;
|
|
for (RowIndex i(0); i < integer_lp_.size(); ++i) {
|
|
int64_t activity = 0;
|
|
for (const auto& term : integer_lp_[i].terms) {
|
|
const int64_t prod =
|
|
CapProd(integer_solution_[term.first.value()], term.second.value());
|
|
if (prod <= std::numeric_limits<int64_t>::min() ||
|
|
prod >= std::numeric_limits<int64_t>::max()) {
|
|
activity = prod;
|
|
break;
|
|
}
|
|
activity = CapAdd(activity, prod);
|
|
if (activity <= std::numeric_limits<int64_t>::min() ||
|
|
activity >= std::numeric_limits<int64_t>::max())
|
|
break;
|
|
}
|
|
if (activity > integer_lp_[i].ub || activity < integer_lp_[i].lb) {
|
|
integer_solution_is_feasible_ = false;
|
|
num_infeasible_constraints_++;
|
|
const int64_t ub_infeasibility =
|
|
activity > integer_lp_[i].ub.value()
|
|
? activity - integer_lp_[i].ub.value()
|
|
: 0;
|
|
const int64_t lb_infeasibility =
|
|
activity < integer_lp_[i].lb.value()
|
|
? integer_lp_[i].lb.value() - activity
|
|
: 0;
|
|
integer_solution_infeasibility_ =
|
|
std::max(integer_solution_infeasibility_,
|
|
std::max(ub_infeasibility, lb_infeasibility));
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace sat
|
|
} // namespace operations_research
|