[GLOP] allow using approximate basis as starting basis

This commit is contained in:
Laurent Perron
2021-08-26 14:00:40 +02:00
parent 8f43182fed
commit 0c79510928
6 changed files with 89 additions and 9 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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.

View File

@@ -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().

View File

@@ -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_; }