diff --git a/ortools/glop/revised_simplex.cc b/ortools/glop/revised_simplex.cc index a9a9aa1184..24f34fa053 100644 --- a/ortools/glop/revised_simplex.cc +++ b/ortools/glop/revised_simplex.cc @@ -123,6 +123,7 @@ RevisedSimplex::RevisedSimplex() void RevisedSimplex::ClearStateForNextSolve() { SCOPED_TIME_STAT(&function_stats_); solution_state_.statuses.clear(); + variable_starting_values_.clear(); } void RevisedSimplex::LoadStateForNextSolve(const BasisState& state) { @@ -131,6 +132,11 @@ void RevisedSimplex::LoadStateForNextSolve(const BasisState& state) { solution_state_has_been_set_externally_ = true; } +void RevisedSimplex::SetStartingVariableValuesForNextSolve( + const DenseRow& values) { + variable_starting_values_ = values; +} + void RevisedSimplex::NotifyThatMatrixIsUnchangedForNextSolve() { notify_that_matrix_is_unchanged_ = true; } @@ -164,6 +170,7 @@ Status RevisedSimplex::Solve(const LinearProgram& lp, TimeLimit* time_limit) { // case we abort without resetting it, setting this allow us to still use the // previous state info, but we will double-check everything. solution_state_has_been_set_externally_ = true; + variable_starting_values_.clear(); if (VLOG_IS_ON(1)) { ComputeNumberOfEmptyRows(); @@ -1370,6 +1377,11 @@ Status RevisedSimplex::Initialize(const LinearProgram& lp) { for (const ColIndex col : variables_info_.GetIsBasicBitRow()) { candidates.push_back(col); } + if (log_info) { + LOG(INFO) << "The warm-start state contains " << candidates.size() + << " candidates for the basis (num_rows = " << num_rows_ + << ")."; + } // Optimization: Try to factorize it right away if we have the correct // number of element. Ideally the other path below would no require a @@ -1390,7 +1402,14 @@ Status RevisedSimplex::Initialize(const LinearProgram& lp) { if (solve_from_scratch) { basis_ = basis_factorization_.ComputeInitialBasis(candidates); - variables_info_.CorrectBasicStatus(basis_); + const int num_super_basic = + variables_info_.CorrectBasicStatus(basis_, variable_starting_values_); + if (log_info) { + LOG(INFO) << "The initial basis did not use " << num_super_basic + << " BASIC columns from the initial state and used " + << (num_rows_ - (candidates.size() - num_super_basic)) + << " slack variables that were not marked BASIC."; + } if (InitializeFirstBasis(basis_).ok()) { solve_from_scratch = false; diff --git a/ortools/glop/revised_simplex.h b/ortools/glop/revised_simplex.h index 8fe4724e70..c0a2437f95 100644 --- a/ortools/glop/revised_simplex.h +++ b/ortools/glop/revised_simplex.h @@ -154,6 +154,11 @@ class RevisedSimplex { // Uses the given state as a warm-start for the next Solve() call. void LoadStateForNextSolve(const BasisState& state); + // Advanced usage. While constructing the initial basis, if this is called + // then we will use these values to decide the status of the BASIC variables + // that are not kept in the initial basis. + void SetStartingVariableValuesForNextSolve(const DenseRow& values); + // Advanced usage. Tells the next Solve() that the matrix inside the linear // program will not change compared to the one used the last time Solve() was // called. This allows to bypass the somewhat costly check of comparing both @@ -627,6 +632,9 @@ class RevisedSimplex { BasisState solution_state_; bool solution_state_has_been_set_externally_; + // If this is cleared, we assume they are none. + DenseRow variable_starting_values_; + // Flag used by NotifyThatMatrixIsUnchangedForNextSolve() and changing // the behavior of Initialize(). bool notify_that_matrix_is_unchanged_ = false; diff --git a/ortools/glop/variables_info.cc b/ortools/glop/variables_info.cc index 92d187f02b..278a304ccd 100644 --- a/ortools/glop/variables_info.cc +++ b/ortools/glop/variables_info.cc @@ -160,17 +160,34 @@ void VariablesInfo::InitializeFromBasisState(ColIndex first_slack_col, } } -void VariablesInfo::CorrectBasicStatus(const RowToColMapping& basis) { +int VariablesInfo::CorrectBasicStatus(const RowToColMapping& basis, + const DenseRow& starting_values) { const ColIndex num_cols = lower_bounds_.size(); is_basic_.ClearAndResize(num_cols); for (const ColIndex col : basis) { UpdateToBasicStatus(col); } + int num_no_longer_in_basis = 0; for (ColIndex col(0); col < num_cols; ++col) { if (!is_basic_[col] && variable_status_[col] == VariableStatus::BASIC) { - UpdateToNonBasicStatus(col, DefaultVariableStatus(col)); + ++num_no_longer_in_basis; + if (col < starting_values.size() && + variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED) { + const Fractional diff_ub = + std::abs(upper_bounds_[col] - starting_values[col]); + const Fractional diff_lb = + std::abs(lower_bounds_[col] - starting_values[col]); + if (diff_lb < diff_ub) { + UpdateToNonBasicStatus(col, VariableStatus::AT_LOWER_BOUND); + } else { + UpdateToNonBasicStatus(col, VariableStatus::AT_UPPER_BOUND); + } + } else { + UpdateToNonBasicStatus(col, DefaultVariableStatus(col)); + } } } + return num_no_longer_in_basis; } void VariablesInfo::InitializeToDefaultStatus() { diff --git a/ortools/glop/variables_info.h b/ortools/glop/variables_info.h index 52575dc544..2316a6a03b 100644 --- a/ortools/glop/variables_info.h +++ b/ortools/glop/variables_info.h @@ -80,9 +80,16 @@ class VariablesInfo { void InitializeFromBasisState(ColIndex first_slack, ColIndex num_new_cols, const BasisState& state); - // Reset to the default status any column not listed in the basis and make - // sure all the one listed are marked as basic. - void CorrectBasicStatus(const RowToColMapping& basis); + // Resets to the default status any column with a BASIC status not listed in + // the basis. Returns their number. + // + // If starting_values is provided, then instead of the default status, we + // will use the bounds closest to starting_values[col] for the BASIC variable + // not in the basic. + // + // Also makes sure all the column listed in basis are marked as basic. + int CorrectBasicStatus(const RowToColMapping& basis, + const DenseRow& starting_values); // Sets all variables status to their lowest magnitude bounds. Note that there // will be no basic variable after this is called. diff --git a/ortools/lp_data/lp_data_utils.cc b/ortools/lp_data/lp_data_utils.cc index 4fa1bd70c6..9946314a1b 100644 --- a/ortools/lp_data/lp_data_utils.cc +++ b/ortools/lp_data/lp_data_utils.cc @@ -94,6 +94,29 @@ Fractional LpScalingHelper::VariableScalingFactor(ColIndex col) const { return scaler_.ColUnscalingFactor(col) * bound_scaling_factor_; } +Fractional LpScalingHelper::ScaleVariableValue(ColIndex col, + Fractional value) const { + return value * scaler_.ColUnscalingFactor(col) * bound_scaling_factor_; +} + +Fractional LpScalingHelper::ScaleReducedCost(ColIndex col, + Fractional value) const { + // The reduced cost move like the objective and the col scale. + return value / scaler_.ColUnscalingFactor(col) * objective_scaling_factor_; +} + +Fractional LpScalingHelper::ScaleDualValue(RowIndex row, + Fractional value) const { + // The dual value move like the objective and the inverse of the row scale. + return value * (scaler_.RowUnscalingFactor(row) * objective_scaling_factor_); +} + +Fractional LpScalingHelper::ScaleConstraintActivity(RowIndex row, + Fractional value) const { + // The activity move with the row_scale and the bound_scaling_factor. + return value / scaler_.RowUnscalingFactor(row) * bound_scaling_factor_; +} + Fractional LpScalingHelper::UnscaleVariableValue(ColIndex col, Fractional value) const { // Just the opposite of ScaleVariableValue(). diff --git a/ortools/lp_data/lp_data_utils.h b/ortools/lp_data/lp_data_utils.h index e238641313..47a982f044 100644 --- a/ortools/lp_data/lp_data_utils.h +++ b/ortools/lp_data/lp_data_utils.h @@ -57,9 +57,11 @@ class LpScalingHelper { // Clear all scaling coefficients. void Clear(); - // A variable value in the original domain must be multiplied by this factor - // to be in the scaled domain. - Fractional VariableScalingFactor(ColIndex col) const; + // Transforms value from unscaled domain to the scaled one. + Fractional ScaleVariableValue(ColIndex col, Fractional value) const; + Fractional ScaleReducedCost(ColIndex col, Fractional value) const; + Fractional ScaleDualValue(RowIndex row, Fractional value) const; + Fractional ScaleConstraintActivity(RowIndex row, Fractional value) const; // Transforms corresponding value from the scaled domain to the original one. Fractional UnscaleVariableValue(ColIndex col, Fractional value) const; @@ -76,6 +78,10 @@ class LpScalingHelper { void UnscaleColumnRightSolve(const RowToColMapping& basis, ColIndex col, ScatteredColumn* right_inverse) const; + // A variable value in the original domain must be multiplied by this factor + // to be in the scaled domain. + Fractional VariableScalingFactor(ColIndex col) const; + // Visible for testing. All objective coefficients of the original LP where // multiplied by this factor. Nothing else changed. Fractional BoundsScalingFactor() const { return bound_scaling_factor_; }