diff --git a/src/glop/lp_solver.cc b/src/glop/lp_solver.cc index fd63c2fbac..66927040ff 100644 --- a/src/glop/lp_solver.cc +++ b/src/glop/lp_solver.cc @@ -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(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, - 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 is needed because the Android port uses int32. - static_cast(new_num_entries.value()), - static_cast(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, diff --git a/src/glop/lp_solver.h b/src/glop/lp_solver.h index 60123441e8..f30d3a2f6f 100644 --- a/src/glop/lp_solver.h +++ b/src/glop/lp_solver.h @@ -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, - 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> preprocessors_; - // The revised simplex solver. std::unique_ptr 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_; diff --git a/src/glop/preprocessor.cc b/src/glop/preprocessor.cc index 8f8a036902..ad94d5cba3 100644 --- a/src/glop/preprocessor.cc +++ b/src/glop/preprocessor.cc @@ -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(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, 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 is needed because the Android port uses int32. + static_cast(new_num_entries.value()), + static_cast(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)); diff --git a/src/glop/preprocessor.h b/src/glop/preprocessor.h index ac3d2a355b..3781934fa5 100644 --- a/src/glop/preprocessor.h +++ b/src/glop/preprocessor.h @@ -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, + 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> 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 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: