Updated GLOP preprocessing code.
This commit is contained in:
@@ -140,28 +140,30 @@ ProblemStatus LPSolver::Solve(const LinearProgram& lp) {
|
||||
// Make an internal copy of the problem for the preprocessing.
|
||||
VLOG(1) << "Initial problem: " << lp.GetDimensionString();
|
||||
VLOG(1) << "Objective stats: " << lp.GetObjectiveStatsString();
|
||||
initial_num_entries_ = lp.num_entries();
|
||||
initial_num_rows_ = lp.num_constraints();
|
||||
initial_num_cols_ = lp.num_variables();
|
||||
current_linear_program_.PopulateFromLinearProgram(lp);
|
||||
|
||||
// Preprocess.
|
||||
status_ = ProblemStatus::INIT;
|
||||
RunPreprocessors(time_limit);
|
||||
MainLpPreprocessor preprocessor;
|
||||
parameters_.set_max_time_in_seconds(time_limit.GetTimeLeft());
|
||||
preprocessor.SetParameters(parameters_);
|
||||
const bool postsolve_is_needed = preprocessor.Run(¤t_linear_program_);
|
||||
|
||||
// At this point, we need to initialize a ProblemSolution with the correct
|
||||
// size and status.
|
||||
ProblemSolution solution(current_linear_program_.num_constraints(),
|
||||
current_linear_program_.num_variables());
|
||||
solution.status = status_;
|
||||
solution.status = preprocessor.status();
|
||||
|
||||
// TODO(user): find a cleaner way to pass around the time left.
|
||||
parameters_.set_max_time_in_seconds(time_limit.GetTimeLeft());
|
||||
RunRevisedSimplexIfNeeded(&solution);
|
||||
PostprocessSolution(&solution);
|
||||
|
||||
if (postsolve_is_needed) preprocessor.RecoverSolution(&solution);
|
||||
return LoadAndVerifySolution(lp, solution);
|
||||
}
|
||||
|
||||
void LPSolver::Clear() {
|
||||
ResizeSolution(RowIndex(0), ColIndex(0));
|
||||
preprocessors_.clear();
|
||||
revised_simplex_.reset(nullptr);
|
||||
}
|
||||
|
||||
@@ -194,7 +196,7 @@ ProblemStatus LPSolver::LoadAndVerifySolution(const LinearProgram& lp,
|
||||
dual_values_ = solution.dual_values;
|
||||
variable_statuses_ = solution.variable_statuses;
|
||||
constraint_statuses_ = solution.constraint_statuses;
|
||||
status_ = solution.status;
|
||||
ProblemStatus status = solution.status;
|
||||
|
||||
// Objective before eventually moving the primal/dual values inside their
|
||||
// bounds.
|
||||
@@ -209,7 +211,7 @@ ProblemStatus LPSolver::LoadAndVerifySolution(const LinearProgram& lp,
|
||||
ProblemObjectiveValue(lp, dual_objective_value));
|
||||
|
||||
// Eventually move the primal/dual values inside their bounds.
|
||||
if (status_ == ProblemStatus::OPTIMAL &&
|
||||
if (status == ProblemStatus::OPTIMAL &&
|
||||
parameters_.provide_strong_optimal_guarantee()) {
|
||||
MovePrimalValuesWithinBounds(lp);
|
||||
MoveDualValuesWithinBounds(lp);
|
||||
@@ -269,7 +271,7 @@ ProblemStatus LPSolver::LoadAndVerifySolution(const LinearProgram& lp,
|
||||
const double objective_error_ub = ComputeMaxExpectedObjectiveError(lp);
|
||||
VLOG(1) << "Objective error <= " << objective_error_ub;
|
||||
|
||||
if (status_ == ProblemStatus::OPTIMAL &&
|
||||
if (status == ProblemStatus::OPTIMAL &&
|
||||
parameters_.provide_strong_optimal_guarantee()) {
|
||||
// If the primal/dual values were moved to the bounds, then the primal/dual
|
||||
// infeasibilities should be exactly zero (but not the residuals).
|
||||
@@ -283,41 +285,40 @@ ProblemStatus LPSolver::LoadAndVerifySolution(const LinearProgram& lp,
|
||||
}
|
||||
if (rhs_perturbation_is_too_large) {
|
||||
VLOG(1) << "The needed rhs perturbation is too large !!";
|
||||
status_ = ProblemStatus::IMPRECISE;
|
||||
status = ProblemStatus::IMPRECISE;
|
||||
}
|
||||
if (cost_perturbation_is_too_large) {
|
||||
VLOG(1) << "The needed cost perturbation is too large !!";
|
||||
status_ = ProblemStatus::IMPRECISE;
|
||||
status = ProblemStatus::IMPRECISE;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we compare the values without offset nor scaling. We also need to
|
||||
// compare them before we move the primal/dual values, otherwise we lose some
|
||||
// precision since the values are modified independently of each other.
|
||||
if (status_ == ProblemStatus::OPTIMAL) {
|
||||
if (status == ProblemStatus::OPTIMAL) {
|
||||
if (std::abs(primal_objective_value - dual_objective_value) >
|
||||
objective_error_ub) {
|
||||
VLOG(1) << "The objective gap of the final solution is too large.";
|
||||
status_ = ProblemStatus::IMPRECISE;
|
||||
status = ProblemStatus::IMPRECISE;
|
||||
}
|
||||
}
|
||||
if ((status_ == ProblemStatus::OPTIMAL ||
|
||||
status_ == ProblemStatus::PRIMAL_FEASIBLE) &&
|
||||
if ((status == ProblemStatus::OPTIMAL ||
|
||||
status == ProblemStatus::PRIMAL_FEASIBLE) &&
|
||||
(primal_residual_is_too_large || primal_infeasibility_is_too_large)) {
|
||||
VLOG(1) << "The primal infeasibility of the final solution is too large.";
|
||||
status_ = ProblemStatus::IMPRECISE;
|
||||
status = ProblemStatus::IMPRECISE;
|
||||
}
|
||||
if ((status_ == ProblemStatus::OPTIMAL ||
|
||||
status_ == ProblemStatus::DUAL_FEASIBLE) &&
|
||||
if ((status == ProblemStatus::OPTIMAL ||
|
||||
status == ProblemStatus::DUAL_FEASIBLE) &&
|
||||
(dual_residual_is_too_large || dual_infeasibility_is_too_large)) {
|
||||
VLOG(1) << "The dual infeasibility of the final solution is too large.";
|
||||
status_ = ProblemStatus::IMPRECISE;
|
||||
status = ProblemStatus::IMPRECISE;
|
||||
}
|
||||
|
||||
may_have_multiple_solutions_ =
|
||||
(status_ == ProblemStatus::OPTIMAL) ?
|
||||
IsOptimalSolutionOnFacet(lp) : false;
|
||||
return status_;
|
||||
(status == ProblemStatus::OPTIMAL) ? IsOptimalSolutionOnFacet(lp) : false;
|
||||
return status;
|
||||
}
|
||||
|
||||
bool LPSolver::IsOptimalSolutionOnFacet(const LinearProgram& lp) {
|
||||
@@ -432,115 +433,6 @@ void LPSolver::ResizeSolution(RowIndex num_rows, ColIndex num_cols) {
|
||||
constraint_statuses_.resize(num_rows, ConstraintStatus::FREE);
|
||||
}
|
||||
|
||||
#define RUN_PREPROCESSOR(name) \
|
||||
RunAndPushIfRelevant(std::unique_ptr<Preprocessor>(new name()), #name, \
|
||||
time_limit)
|
||||
|
||||
// TODO(user): This should probably be moved to a MainLpPreprocessor class so it
|
||||
// can be used in other places.
|
||||
void LPSolver::RunPreprocessors(const TimeLimit& time_limit) {
|
||||
if (parameters_.use_preprocessing()) {
|
||||
RUN_PREPROCESSOR(ShiftVariableBoundsPreprocessor);
|
||||
|
||||
// We run it a few times because running one preprocessor may allow another
|
||||
// one to remove more stuff.
|
||||
const int kMaxNumPasses = 20;
|
||||
for (int i = 0; i < kMaxNumPasses; ++i) {
|
||||
const int old_stack_size = preprocessors_.size();
|
||||
RUN_PREPROCESSOR(FixedVariablePreprocessor);
|
||||
RUN_PREPROCESSOR(SingletonPreprocessor);
|
||||
RUN_PREPROCESSOR(ForcingAndImpliedFreeConstraintPreprocessor);
|
||||
RUN_PREPROCESSOR(FreeConstraintPreprocessor);
|
||||
RUN_PREPROCESSOR(UnconstrainedVariablePreprocessor);
|
||||
RUN_PREPROCESSOR(DoubletonEqualityRowPreprocessor);
|
||||
RUN_PREPROCESSOR(ImpliedFreePreprocessor);
|
||||
RUN_PREPROCESSOR(DoubletonFreeColumnPreprocessor);
|
||||
|
||||
// Abort early if none of the preprocessors did something. Technically
|
||||
// this is true if none of the preprocessors above needs postsolving,
|
||||
// which has exactly the same meaning for these particular preprocessors.
|
||||
if (preprocessors_.size() == old_stack_size) {
|
||||
// We use i here because the last pass did nothing.
|
||||
VLOG(1) << "Reached fixed point after presolve pass #" << i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RUN_PREPROCESSOR(EmptyColumnPreprocessor);
|
||||
RUN_PREPROCESSOR(EmptyConstraintPreprocessor);
|
||||
|
||||
// TODO(user): Run them in the loop above if the effect on the running time
|
||||
// is good. This needs more investigation.
|
||||
RUN_PREPROCESSOR(ProportionalColumnPreprocessor);
|
||||
RUN_PREPROCESSOR(ProportionalRowPreprocessor);
|
||||
|
||||
// If DualizerPreprocessor was run, we need to do some extra preprocessing.
|
||||
// This is because it currently adds a lot of zero-cost singleton columns.
|
||||
const int old_stack_size = preprocessors_.size();
|
||||
|
||||
// TODO(user): We probably want to scale the costs before and after this
|
||||
// preprocessor so that the rhs/objective of the dual are with a good
|
||||
// magnitude.
|
||||
RUN_PREPROCESSOR(DualizerPreprocessor);
|
||||
if (old_stack_size != preprocessors_.size()) {
|
||||
RUN_PREPROCESSOR(SingletonPreprocessor);
|
||||
RUN_PREPROCESSOR(FreeConstraintPreprocessor);
|
||||
RUN_PREPROCESSOR(UnconstrainedVariablePreprocessor);
|
||||
RUN_PREPROCESSOR(EmptyColumnPreprocessor);
|
||||
RUN_PREPROCESSOR(EmptyConstraintPreprocessor);
|
||||
}
|
||||
}
|
||||
|
||||
// These are implemented as preprocessors, but are not controlled by the
|
||||
// use_preprocessing() parameter.
|
||||
RUN_PREPROCESSOR(SingletonColumnSignPreprocessor);
|
||||
RUN_PREPROCESSOR(ScalingPreprocessor);
|
||||
}
|
||||
|
||||
#undef RUN_PREPROCESSOR
|
||||
|
||||
void LPSolver::RunAndPushIfRelevant(std::unique_ptr<Preprocessor> preprocessor,
|
||||
const std::string& name,
|
||||
const TimeLimit& time_limit) {
|
||||
RETURN_IF_NULL(preprocessor);
|
||||
WallTimer timer;
|
||||
timer.Start();
|
||||
parameters_.set_max_time_in_seconds(time_limit.GetTimeLeft());
|
||||
preprocessor->SetParameters(parameters_);
|
||||
if (status_ == ProblemStatus::INIT) {
|
||||
// No need to run the preprocessor if current_linear_program_ is empty.
|
||||
// TODO(user): without this test, the code is failing as of 2013-03-18.
|
||||
if (current_linear_program_.num_variables() == 0 &&
|
||||
current_linear_program_.num_constraints() == 0) {
|
||||
status_ = ProblemStatus::OPTIMAL;
|
||||
} else if (preprocessor->Run(¤t_linear_program_)) {
|
||||
const EntryIndex new_num_entries = current_linear_program_.num_entries();
|
||||
VLOG(1) << StringPrintf(
|
||||
"%s(%fs): %d(%d) rows, %d(%d) columns, %lld(%lld) entries.",
|
||||
name.c_str(), timer.Get(),
|
||||
current_linear_program_.num_constraints().value(),
|
||||
(current_linear_program_.num_constraints() - initial_num_rows_)
|
||||
.value(),
|
||||
current_linear_program_.num_variables().value(),
|
||||
(current_linear_program_.num_variables() - initial_num_cols_).value(),
|
||||
// static_cast<int64> is needed because the Android port uses int32.
|
||||
static_cast<int64>(new_num_entries.value()),
|
||||
static_cast<int64>(new_num_entries.value() -
|
||||
initial_num_entries_.value()));
|
||||
status_ = preprocessor->status();
|
||||
preprocessors_.push_back(std::move(preprocessor));
|
||||
return;
|
||||
} else {
|
||||
// Even if a preprocessor returns false (i.e. no need for postsolve), it
|
||||
// can detect an issue with the problem.
|
||||
status_ = preprocessor->status();
|
||||
if (status_ != ProblemStatus::INIT) {
|
||||
VLOG(1) << name << " detected that the problem is "
|
||||
<< GetProblemStatusString(status_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LPSolver::RunRevisedSimplexIfNeeded(ProblemSolution* solution) {
|
||||
// Note that the transpose matrix is no longer needed at this point.
|
||||
// This helps reduce the peak memory usage of the solver.
|
||||
@@ -575,13 +467,6 @@ void LPSolver::RunRevisedSimplexIfNeeded(ProblemSolution* solution) {
|
||||
}
|
||||
}
|
||||
|
||||
void LPSolver::PostprocessSolution(ProblemSolution* solution) {
|
||||
while (!preprocessors_.empty()) {
|
||||
preprocessors_.back()->StoreSolution(solution);
|
||||
preprocessors_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void LogVariableStatusError(ColIndex col, Fractional value,
|
||||
|
||||
@@ -138,22 +138,10 @@ class LPSolver {
|
||||
void MovePrimalValuesWithinBounds(const LinearProgram& lp);
|
||||
void MoveDualValuesWithinBounds(const LinearProgram& lp);
|
||||
|
||||
// Runs all preprocessors in sequence.
|
||||
void RunPreprocessors(const TimeLimit& time_limit);
|
||||
|
||||
// Runs the given preprocessor and pushes it when relevant (i.e. when it did
|
||||
// something) on the preprocessors_ stack.
|
||||
void RunAndPushIfRelevant(std::unique_ptr<Preprocessor> preprocessor,
|
||||
const std::string& name, const TimeLimit& time_limit);
|
||||
|
||||
// Runs the revised simplex algorithm if needed (i.e. if the program was not
|
||||
// already solved by the preprocessors).
|
||||
void RunRevisedSimplexIfNeeded(ProblemSolution* solution);
|
||||
|
||||
// Postprocess the solution by calling the StoreSolution() of the
|
||||
// preprocessors in the reverse order in which their where applied.
|
||||
void PostprocessSolution(ProblemSolution* solution);
|
||||
|
||||
// Checks that the returned solution values and statuses are consistent.
|
||||
// Returns true if this is the case. See the code for the exact check
|
||||
// performed.
|
||||
@@ -219,20 +207,16 @@ class LPSolver {
|
||||
double ComputeReducedCostInfeasibility(const LinearProgram& lp,
|
||||
bool* is_too_large);
|
||||
|
||||
// Dimension of the linear program given to the last Solve().
|
||||
// This is used for displaying purpose only.
|
||||
EntryIndex initial_num_entries_;
|
||||
RowIndex initial_num_rows_;
|
||||
ColIndex initial_num_cols_;
|
||||
|
||||
// On a call to Solve(), this is initialized to an exact copy of the given
|
||||
// linear program. It is later modified by the preprocessors and then solved
|
||||
// by the revised simplex.
|
||||
//
|
||||
// This is not efficient memory-wise but allows to check optimality with
|
||||
// respect to the given LinearProgram that is guaranteed to not have been
|
||||
// modified. It also allows for a nicer Solve() API with a const
|
||||
// LinearProgram& input.
|
||||
LinearProgram current_linear_program_;
|
||||
|
||||
// Stack of preprocessors currently applied to the current linear program.
|
||||
std::vector<std::unique_ptr<Preprocessor>> preprocessors_;
|
||||
|
||||
// The revised simplex solver.
|
||||
std::unique_ptr<RevisedSimplex> revised_simplex_;
|
||||
|
||||
@@ -241,7 +225,6 @@ class LPSolver {
|
||||
|
||||
// The current ProblemSolution.
|
||||
// TODO(user): use a ProblemSolution directly?
|
||||
ProblemStatus status_;
|
||||
DenseRow primal_values_;
|
||||
DenseColumn dual_values_;
|
||||
VariableStatusRow variable_statuses_;
|
||||
|
||||
@@ -41,6 +41,132 @@ Preprocessor::Preprocessor()
|
||||
: status_(ProblemStatus::INIT), parameters_(), in_mip_context_(false) {}
|
||||
Preprocessor::~Preprocessor() {}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// MainLpPreprocessor
|
||||
// --------------------------------------------------------
|
||||
|
||||
#define RUN_PREPROCESSOR(name) \
|
||||
RunAndPushIfRelevant(std::unique_ptr<Preprocessor>(new name()), #name, \
|
||||
&time_limit, lp)
|
||||
|
||||
bool MainLpPreprocessor::Run(LinearProgram* lp) {
|
||||
RETURN_VALUE_IF_NULL(lp, false);
|
||||
TimeLimit time_limit(parameters_.max_time_in_seconds());
|
||||
initial_num_rows_ = lp->num_constraints();
|
||||
initial_num_cols_ = lp->num_variables();
|
||||
initial_num_entries_ = lp->num_entries();
|
||||
if (parameters_.use_preprocessing()) {
|
||||
RUN_PREPROCESSOR(ShiftVariableBoundsPreprocessor);
|
||||
|
||||
// We run it a few times because running one preprocessor may allow another
|
||||
// one to remove more stuff.
|
||||
const int kMaxNumPasses = 20;
|
||||
for (int i = 0; i < kMaxNumPasses; ++i) {
|
||||
const int old_stack_size = preprocessors_.size();
|
||||
RUN_PREPROCESSOR(FixedVariablePreprocessor);
|
||||
RUN_PREPROCESSOR(SingletonPreprocessor);
|
||||
RUN_PREPROCESSOR(ForcingAndImpliedFreeConstraintPreprocessor);
|
||||
RUN_PREPROCESSOR(FreeConstraintPreprocessor);
|
||||
RUN_PREPROCESSOR(UnconstrainedVariablePreprocessor);
|
||||
RUN_PREPROCESSOR(DoubletonEqualityRowPreprocessor);
|
||||
RUN_PREPROCESSOR(ImpliedFreePreprocessor);
|
||||
RUN_PREPROCESSOR(DoubletonFreeColumnPreprocessor);
|
||||
|
||||
// Abort early if none of the preprocessors did something. Technically
|
||||
// this is true if none of the preprocessors above needs postsolving,
|
||||
// which has exactly the same meaning for these particular preprocessors.
|
||||
if (preprocessors_.size() == old_stack_size) {
|
||||
// We use i here because the last pass did nothing.
|
||||
VLOG(1) << "Reached fixed point after presolve pass #" << i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RUN_PREPROCESSOR(EmptyColumnPreprocessor);
|
||||
RUN_PREPROCESSOR(EmptyConstraintPreprocessor);
|
||||
|
||||
// TODO(user): Run them in the loop above if the effect on the running time
|
||||
// is good. This needs more investigation.
|
||||
RUN_PREPROCESSOR(ProportionalColumnPreprocessor);
|
||||
RUN_PREPROCESSOR(ProportionalRowPreprocessor);
|
||||
|
||||
// If DualizerPreprocessor was run, we need to do some extra preprocessing.
|
||||
// This is because it currently adds a lot of zero-cost singleton columns.
|
||||
const int old_stack_size = preprocessors_.size();
|
||||
|
||||
// TODO(user): We probably want to scale the costs before and after this
|
||||
// preprocessor so that the rhs/objective of the dual are with a good
|
||||
// magnitude.
|
||||
RUN_PREPROCESSOR(DualizerPreprocessor);
|
||||
if (old_stack_size != preprocessors_.size()) {
|
||||
RUN_PREPROCESSOR(SingletonPreprocessor);
|
||||
RUN_PREPROCESSOR(FreeConstraintPreprocessor);
|
||||
RUN_PREPROCESSOR(UnconstrainedVariablePreprocessor);
|
||||
RUN_PREPROCESSOR(EmptyColumnPreprocessor);
|
||||
RUN_PREPROCESSOR(EmptyConstraintPreprocessor);
|
||||
}
|
||||
}
|
||||
|
||||
// These are implemented as preprocessors, but are not controlled by the
|
||||
// use_preprocessing() parameter.
|
||||
RUN_PREPROCESSOR(SingletonColumnSignPreprocessor);
|
||||
RUN_PREPROCESSOR(ScalingPreprocessor);
|
||||
|
||||
return !preprocessors_.empty();
|
||||
}
|
||||
|
||||
#undef RUN_PREPROCESSOR
|
||||
|
||||
void MainLpPreprocessor::RunAndPushIfRelevant(
|
||||
std::unique_ptr<Preprocessor> preprocessor, const std::string& name,
|
||||
TimeLimit* time_limit, LinearProgram* lp) {
|
||||
RETURN_IF_NULL(preprocessor);
|
||||
if (status_ != ProblemStatus::INIT || time_limit->LimitReached()) return;
|
||||
|
||||
WallTimer timer;
|
||||
timer.Start();
|
||||
parameters_.set_max_time_in_seconds(time_limit->GetTimeLeft());
|
||||
preprocessor->SetParameters(parameters_);
|
||||
|
||||
// No need to run the preprocessor if the lp is empty.
|
||||
// TODO(user): without this test, the code is failing as of 2013-03-18.
|
||||
if (lp->num_variables() == 0 && lp->num_constraints() == 0) {
|
||||
status_ = ProblemStatus::OPTIMAL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (preprocessor->Run(lp)) {
|
||||
const EntryIndex new_num_entries = lp->num_entries();
|
||||
VLOG(1) << StringPrintf(
|
||||
"%s(%fs): %d(%d) rows, %d(%d) columns, %lld(%lld) entries.",
|
||||
name.c_str(), timer.Get(), lp->num_constraints().value(),
|
||||
(lp->num_constraints() - initial_num_rows_).value(),
|
||||
lp->num_variables().value(),
|
||||
(lp->num_variables() - initial_num_cols_).value(),
|
||||
// static_cast<int64> is needed because the Android port uses int32.
|
||||
static_cast<int64>(new_num_entries.value()),
|
||||
static_cast<int64>(new_num_entries.value() -
|
||||
initial_num_entries_.value()));
|
||||
status_ = preprocessor->status();
|
||||
preprocessors_.push_back(std::move(preprocessor));
|
||||
return;
|
||||
} else {
|
||||
// Even if a preprocessor returns false (i.e. no need for postsolve), it
|
||||
// can detect an issue with the problem.
|
||||
status_ = preprocessor->status();
|
||||
if (status_ != ProblemStatus::INIT) {
|
||||
VLOG(1) << name << " detected that the problem is "
|
||||
<< GetProblemStatusString(status_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainLpPreprocessor::RecoverSolution(ProblemSolution* solution) const {
|
||||
while (!preprocessors_.empty()) {
|
||||
preprocessors_.back()->RecoverSolution(solution);
|
||||
preprocessors_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// ColumnDeletionHelper
|
||||
// --------------------------------------------------------
|
||||
@@ -249,7 +375,7 @@ bool EmptyColumnPreprocessor::Run(LinearProgram* linear_program) {
|
||||
return !column_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void EmptyColumnPreprocessor::StoreSolution(ProblemSolution* solution) const {
|
||||
void EmptyColumnPreprocessor::RecoverSolution(ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
column_deletion_helper_.RestoreDeletedColumns(solution);
|
||||
}
|
||||
@@ -530,7 +656,7 @@ bool ProportionalColumnPreprocessor::Run(LinearProgram* lp) {
|
||||
return !column_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void ProportionalColumnPreprocessor::StoreSolution(
|
||||
void ProportionalColumnPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
column_deletion_helper_.RestoreDeletedColumns(solution);
|
||||
@@ -818,7 +944,7 @@ bool ProportionalRowPreprocessor::Run(LinearProgram* lp) {
|
||||
return !row_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void ProportionalRowPreprocessor::StoreSolution(
|
||||
void ProportionalRowPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
row_deletion_helper_.RestoreDeletedRows(solution);
|
||||
@@ -912,7 +1038,8 @@ bool FixedVariablePreprocessor::Run(LinearProgram* lp) {
|
||||
return !column_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void FixedVariablePreprocessor::StoreSolution(ProblemSolution* solution) const {
|
||||
void FixedVariablePreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
column_deletion_helper_.RestoreDeletedColumns(solution);
|
||||
}
|
||||
@@ -1058,7 +1185,7 @@ bool ForcingAndImpliedFreeConstraintPreprocessor::Run(LinearProgram* lp) {
|
||||
return !column_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void ForcingAndImpliedFreeConstraintPreprocessor::StoreSolution(
|
||||
void ForcingAndImpliedFreeConstraintPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
column_deletion_helper_.RestoreDeletedColumns(solution);
|
||||
@@ -1325,7 +1452,7 @@ bool ImpliedFreePreprocessor::Run(LinearProgram* lp) {
|
||||
return num_implied_free_variables > 0;
|
||||
}
|
||||
|
||||
void ImpliedFreePreprocessor::StoreSolution(ProblemSolution* solution) const {
|
||||
void ImpliedFreePreprocessor::RecoverSolution(ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
const ColIndex num_cols = solution->variable_statuses.size();
|
||||
for (ColIndex col(0); col < num_cols; ++col) {
|
||||
@@ -1454,7 +1581,7 @@ bool DoubletonFreeColumnPreprocessor::Run(LinearProgram* lp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DoubletonFreeColumnPreprocessor::StoreSolution(
|
||||
void DoubletonFreeColumnPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
row_deletion_helper_.RestoreDeletedRows(solution);
|
||||
for (const RestoreInfo& r : Reverse(restore_stack_)) {
|
||||
@@ -1627,7 +1754,7 @@ bool UnconstrainedVariablePreprocessor::Run(LinearProgram* lp) {
|
||||
return false;
|
||||
} else {
|
||||
// We can remove this column and all its constraints! We just need to
|
||||
// choose a variable value during the call to StoreSolution() that
|
||||
// choose a variable value during the call to RecoverSolution() that
|
||||
// makes all the constraint satisfiable. Test this on bnl2.mps.
|
||||
if (!in_mip_context_) {
|
||||
// TODO(user): this also works if the variable is integer, but we
|
||||
@@ -1663,7 +1790,7 @@ bool UnconstrainedVariablePreprocessor::Run(LinearProgram* lp) {
|
||||
return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void UnconstrainedVariablePreprocessor::StoreSolution(
|
||||
void UnconstrainedVariablePreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
column_deletion_helper_.RestoreDeletedColumns(solution);
|
||||
@@ -1746,7 +1873,7 @@ bool FreeConstraintPreprocessor::Run(LinearProgram* linear_program) {
|
||||
return !row_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void FreeConstraintPreprocessor::StoreSolution(
|
||||
void FreeConstraintPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
row_deletion_helper_.RestoreDeletedRows(solution);
|
||||
@@ -1791,7 +1918,7 @@ bool EmptyConstraintPreprocessor::Run(LinearProgram* lp) {
|
||||
return !row_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void EmptyConstraintPreprocessor::StoreSolution(
|
||||
void EmptyConstraintPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
row_deletion_helper_.RestoreDeletedRows(solution);
|
||||
@@ -2361,7 +2488,7 @@ bool SingletonPreprocessor::Run(LinearProgram* lp) {
|
||||
return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void SingletonPreprocessor::StoreSolution(ProblemSolution* solution) const {
|
||||
void SingletonPreprocessor::RecoverSolution(ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
|
||||
// Note that the two deletion helpers must restore 0.0 values in the positions
|
||||
@@ -2482,7 +2609,7 @@ bool RemoveNearZeroEntriesPreprocessor::Run(LinearProgram* lp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RemoveNearZeroEntriesPreprocessor::StoreSolution(
|
||||
void RemoveNearZeroEntriesPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {}
|
||||
|
||||
// --------------------------------------------------------
|
||||
@@ -2517,7 +2644,7 @@ bool SingletonColumnSignPreprocessor::Run(LinearProgram* linear_program) {
|
||||
return !changed_columns_.empty();
|
||||
}
|
||||
|
||||
void SingletonColumnSignPreprocessor::StoreSolution(
|
||||
void SingletonColumnSignPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
for (int i = 0; i < changed_columns_.size(); ++i) {
|
||||
@@ -2700,7 +2827,7 @@ bool DoubletonEqualityRowPreprocessor::Run(LinearProgram* lp) {
|
||||
return !column_deletion_helper_.IsEmpty();
|
||||
}
|
||||
|
||||
void DoubletonEqualityRowPreprocessor::StoreSolution(
|
||||
void DoubletonEqualityRowPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
column_deletion_helper_.RestoreDeletedColumns(solution);
|
||||
@@ -2894,7 +3021,7 @@ bool DualizerPreprocessor::Run(LinearProgram* lp) {
|
||||
// Note(user): This assumes that LinearProgram.PopulateFromDual() uses
|
||||
// the first ColIndex and RowIndex for the rows and columns of the given
|
||||
// problem.
|
||||
void DualizerPreprocessor::StoreSolution(ProblemSolution* solution) const {
|
||||
void DualizerPreprocessor::RecoverSolution(ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
|
||||
DenseRow new_primal_values(primal_num_cols_, 0.0);
|
||||
@@ -3094,7 +3221,7 @@ bool ShiftVariableBoundsPreprocessor::Run(LinearProgram* lp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShiftVariableBoundsPreprocessor::StoreSolution(
|
||||
void ShiftVariableBoundsPreprocessor::RecoverSolution(
|
||||
ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
const ColIndex num_cols = solution->variable_statuses.size();
|
||||
@@ -3167,7 +3294,7 @@ bool ScalingPreprocessor::Run(LinearProgram* lp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScalingPreprocessor::StoreSolution(ProblemSolution* solution) const {
|
||||
void ScalingPreprocessor::RecoverSolution(ProblemSolution* solution) const {
|
||||
RETURN_IF_NULL(solution);
|
||||
scaler_.ScaleRowVector(false, &(solution->primal_values));
|
||||
scaler_.ScaleColumnVector(false, &(solution->dual_values));
|
||||
|
||||
@@ -21,13 +21,14 @@
|
||||
#ifndef OR_TOOLS_GLOP_PREPROCESSOR_H_
|
||||
#define OR_TOOLS_GLOP_PREPROCESSOR_H_
|
||||
|
||||
#include "base/unique_ptr.h"
|
||||
|
||||
#include "glop/parameters.pb.h"
|
||||
#include "glop/revised_simplex.h"
|
||||
#include "lp_data/lp_data.h"
|
||||
#include "lp_data/lp_types.h"
|
||||
#include "lp_data/matrix_scaler.h"
|
||||
|
||||
|
||||
namespace operations_research {
|
||||
namespace glop {
|
||||
|
||||
@@ -44,7 +45,7 @@ class Preprocessor {
|
||||
virtual ~Preprocessor();
|
||||
|
||||
// Runs the preprocessor by modifying the given linear program. Returns true
|
||||
// if a postsolve step will be needed (i.e. StoreSolution() is not the
|
||||
// if a postsolve step will be needed (i.e. RecoverSolution() is not the
|
||||
// identity function). Also updates status_ to something different from
|
||||
// ProblemStatus::INIT if the problem was solved (including bad statuses
|
||||
// like ProblemStatus::ABNORMAL, ProblemStatus::INFEASIBLE, etc.).
|
||||
@@ -53,7 +54,7 @@ class Preprocessor {
|
||||
// Stores the optimal solution of the linear program that was passed to
|
||||
// Run(). The given solution needs to be set to the optimal solution of the
|
||||
// linear program "modified" by Run().
|
||||
virtual void StoreSolution(ProblemSolution* solution) const = 0;
|
||||
virtual void RecoverSolution(ProblemSolution* solution) const = 0;
|
||||
|
||||
// Returns the status of the preprocessor.
|
||||
// A status different from ProblemStatus::INIT means that the problem is
|
||||
@@ -82,6 +83,40 @@ class Preprocessor {
|
||||
DISALLOW_COPY_AND_ASSIGN(Preprocessor);
|
||||
};
|
||||
|
||||
// --------------------------------------------------------
|
||||
// MainLpPreprocessor
|
||||
// --------------------------------------------------------
|
||||
// This is the main LP preprocessor responsible for calling all the other
|
||||
// preprocessors in this file, possibly more than once.
|
||||
class MainLpPreprocessor : public Preprocessor {
|
||||
public:
|
||||
MainLpPreprocessor() {}
|
||||
~MainLpPreprocessor() override {}
|
||||
bool Run(LinearProgram* linear_program) override;
|
||||
void RecoverSolution(ProblemSolution* solution) const override;
|
||||
|
||||
private:
|
||||
// Runs the given preprocessor and push it on preprocessors_ for the postsolve
|
||||
// step when needed.
|
||||
void RunAndPushIfRelevant(std::unique_ptr<Preprocessor> preprocessor,
|
||||
const std::string& name, TimeLimit* time_limit,
|
||||
LinearProgram* lp);
|
||||
|
||||
// Stack of preprocessors currently applied to the lp that needs postsolve.
|
||||
//
|
||||
// TODO(user): This is mutable so that the preprocessor can be freed as soon
|
||||
// as their RecoverSolution() is called. Make RecoverSolution() non-const or
|
||||
// remove this optimization?
|
||||
mutable std::vector<std::unique_ptr<Preprocessor>> preprocessors_;
|
||||
|
||||
// Initial dimension of the lp given to Run(), for displaying purpose.
|
||||
EntryIndex initial_num_entries_;
|
||||
RowIndex initial_num_rows_;
|
||||
ColIndex initial_num_cols_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MainLpPreprocessor);
|
||||
};
|
||||
|
||||
// --------------------------------------------------------
|
||||
// ColumnDeletionHelper
|
||||
// --------------------------------------------------------
|
||||
@@ -185,7 +220,7 @@ class EmptyColumnPreprocessor : public Preprocessor {
|
||||
EmptyColumnPreprocessor() {}
|
||||
~EmptyColumnPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
ColumnDeletionHelper column_deletion_helper_;
|
||||
@@ -210,7 +245,7 @@ class ProportionalColumnPreprocessor : public Preprocessor {
|
||||
ProportionalColumnPreprocessor() {}
|
||||
~ProportionalColumnPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
void UseInMipContext() final { LOG(FATAL) << "Not implemented."; }
|
||||
|
||||
private:
|
||||
@@ -248,7 +283,7 @@ class ProportionalRowPreprocessor : public Preprocessor {
|
||||
ProportionalRowPreprocessor() {}
|
||||
~ProportionalRowPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
// Informations about proportional rows, only filled for such rows.
|
||||
@@ -341,7 +376,7 @@ class SingletonPreprocessor : public Preprocessor {
|
||||
SingletonPreprocessor() {}
|
||||
~SingletonPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
void UseInMipContext() final { LOG(FATAL) << "Not implemented."; }
|
||||
|
||||
private:
|
||||
@@ -422,7 +457,7 @@ class FixedVariablePreprocessor : public Preprocessor {
|
||||
FixedVariablePreprocessor() {}
|
||||
~FixedVariablePreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
ColumnDeletionHelper column_deletion_helper_;
|
||||
@@ -454,7 +489,7 @@ class ForcingAndImpliedFreeConstraintPreprocessor : public Preprocessor {
|
||||
ForcingAndImpliedFreeConstraintPreprocessor() {}
|
||||
~ForcingAndImpliedFreeConstraintPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
bool lp_is_maximization_problem_;
|
||||
@@ -496,12 +531,12 @@ class ImpliedFreePreprocessor : public Preprocessor {
|
||||
ImpliedFreePreprocessor() {}
|
||||
~ImpliedFreePreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
void UseInMipContext() final { LOG(FATAL) << "Not implemented."; }
|
||||
|
||||
private:
|
||||
// This preprocessor adds fixed offsets to some variables. We remember those
|
||||
// here to un-offset them in StoreSolution().
|
||||
// here to un-offset them in RecoverSolution().
|
||||
DenseRow variable_offsets_;
|
||||
|
||||
// This preprocessor causes some variables who would normally be
|
||||
@@ -542,7 +577,7 @@ class DoubletonFreeColumnPreprocessor : public Preprocessor {
|
||||
DoubletonFreeColumnPreprocessor() {}
|
||||
~DoubletonFreeColumnPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
void UseInMipContext() final { LOG(FATAL) << "Not implemented."; }
|
||||
|
||||
private:
|
||||
@@ -587,7 +622,7 @@ class UnconstrainedVariablePreprocessor : public Preprocessor {
|
||||
UnconstrainedVariablePreprocessor() {}
|
||||
~UnconstrainedVariablePreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
// Removes the given variable and all the rows in which it appears: If a
|
||||
// variable is unconstrained with a zero cost, then all the constraints in
|
||||
@@ -625,7 +660,7 @@ class FreeConstraintPreprocessor : public Preprocessor {
|
||||
FreeConstraintPreprocessor() {}
|
||||
~FreeConstraintPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
RowDeletionHelper row_deletion_helper_;
|
||||
@@ -641,7 +676,7 @@ class EmptyConstraintPreprocessor : public Preprocessor {
|
||||
EmptyConstraintPreprocessor() {}
|
||||
~EmptyConstraintPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
RowDeletionHelper row_deletion_helper_;
|
||||
@@ -663,7 +698,7 @@ class RemoveNearZeroEntriesPreprocessor : public Preprocessor {
|
||||
RemoveNearZeroEntriesPreprocessor() {}
|
||||
~RemoveNearZeroEntriesPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(RemoveNearZeroEntriesPreprocessor);
|
||||
@@ -681,7 +716,7 @@ class SingletonColumnSignPreprocessor : public Preprocessor {
|
||||
SingletonColumnSignPreprocessor() {}
|
||||
~SingletonColumnSignPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
std::vector<ColIndex> changed_columns_;
|
||||
@@ -699,7 +734,7 @@ class DoubletonEqualityRowPreprocessor : public Preprocessor {
|
||||
DoubletonEqualityRowPreprocessor() {}
|
||||
~DoubletonEqualityRowPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
void UseInMipContext() final { LOG(FATAL) << "Not implemented."; }
|
||||
|
||||
private:
|
||||
@@ -764,7 +799,7 @@ class DualizerPreprocessor : public Preprocessor {
|
||||
DualizerPreprocessor() {}
|
||||
~DualizerPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
void UseInMipContext() final {
|
||||
LOG(FATAL) << "In the presence of integer variables, "
|
||||
<< "there is no notion of a dual problem.";
|
||||
@@ -821,7 +856,7 @@ class ShiftVariableBoundsPreprocessor : public Preprocessor {
|
||||
ShiftVariableBoundsPreprocessor() {}
|
||||
~ShiftVariableBoundsPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
|
||||
private:
|
||||
// Contains for each variable by how much its bounds where shifted during
|
||||
@@ -845,7 +880,7 @@ class ScalingPreprocessor : public Preprocessor {
|
||||
ScalingPreprocessor() {}
|
||||
~ScalingPreprocessor() final {}
|
||||
bool Run(LinearProgram* linear_program) final;
|
||||
void StoreSolution(ProblemSolution* solution) const final;
|
||||
void RecoverSolution(ProblemSolution* solution) const final;
|
||||
void UseInMipContext() final { LOG(FATAL) << "Not implemented."; }
|
||||
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user