270 lines
10 KiB
C++
270 lines
10 KiB
C++
// Copyright 2010-2021 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/glop/variable_values.h"
|
|
|
|
#include "ortools/graph/iterators.h"
|
|
#include "ortools/lp_data/lp_utils.h"
|
|
|
|
namespace operations_research {
|
|
namespace glop {
|
|
|
|
VariableValues::VariableValues(const GlopParameters& parameters,
|
|
const CompactSparseMatrix& matrix,
|
|
const RowToColMapping& basis,
|
|
const VariablesInfo& variables_info,
|
|
const BasisFactorization& basis_factorization,
|
|
DualEdgeNorms* dual_edge_norms,
|
|
DynamicMaximum<RowIndex>* dual_prices)
|
|
: parameters_(parameters),
|
|
matrix_(matrix),
|
|
basis_(basis),
|
|
variables_info_(variables_info),
|
|
basis_factorization_(basis_factorization),
|
|
dual_edge_norms_(dual_edge_norms),
|
|
dual_prices_(dual_prices),
|
|
stats_("VariableValues") {}
|
|
|
|
void VariableValues::SetNonBasicVariableValueFromStatus(ColIndex col) {
|
|
SCOPED_TIME_STAT(&stats_);
|
|
const DenseRow& lower_bounds = variables_info_.GetVariableLowerBounds();
|
|
const DenseRow& upper_bounds = variables_info_.GetVariableUpperBounds();
|
|
variable_values_.resize(matrix_.num_cols(), 0.0);
|
|
switch (variables_info_.GetStatusRow()[col]) {
|
|
case VariableStatus::FIXED_VALUE:
|
|
DCHECK_NE(-kInfinity, lower_bounds[col]);
|
|
DCHECK_EQ(lower_bounds[col], upper_bounds[col]);
|
|
variable_values_[col] = lower_bounds[col];
|
|
break;
|
|
case VariableStatus::AT_LOWER_BOUND:
|
|
DCHECK_NE(-kInfinity, lower_bounds[col]);
|
|
variable_values_[col] = lower_bounds[col];
|
|
break;
|
|
case VariableStatus::AT_UPPER_BOUND:
|
|
DCHECK_NE(kInfinity, upper_bounds[col]);
|
|
variable_values_[col] = upper_bounds[col];
|
|
break;
|
|
case VariableStatus::FREE:
|
|
LOG(DFATAL) << "SetNonBasicVariableValueFromStatus() shouldn't "
|
|
<< "be called on a FREE variable.";
|
|
break;
|
|
case VariableStatus::BASIC:
|
|
LOG(DFATAL) << "SetNonBasicVariableValueFromStatus() shouldn't "
|
|
<< "be called on a BASIC variable.";
|
|
break;
|
|
}
|
|
// Note that there is no default value in the switch() statement above to
|
|
// get a compile-time error if a value is missing.
|
|
}
|
|
|
|
void VariableValues::ResetAllNonBasicVariableValues(
|
|
const DenseRow& free_initial_value) {
|
|
const DenseRow& lower_bounds = variables_info_.GetVariableLowerBounds();
|
|
const DenseRow& upper_bounds = variables_info_.GetVariableUpperBounds();
|
|
const VariableStatusRow& statuses = variables_info_.GetStatusRow();
|
|
const ColIndex num_cols = matrix_.num_cols();
|
|
variable_values_.resize(num_cols, 0.0);
|
|
for (ColIndex col(0); col < num_cols; ++col) {
|
|
switch (statuses[col]) {
|
|
case VariableStatus::FIXED_VALUE:
|
|
ABSL_FALLTHROUGH_INTENDED;
|
|
case VariableStatus::AT_LOWER_BOUND:
|
|
variable_values_[col] = lower_bounds[col];
|
|
break;
|
|
case VariableStatus::AT_UPPER_BOUND:
|
|
variable_values_[col] = upper_bounds[col];
|
|
break;
|
|
case VariableStatus::FREE:
|
|
variable_values_[col] =
|
|
col < free_initial_value.size() ? free_initial_value[col] : 0.0;
|
|
break;
|
|
case VariableStatus::BASIC:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VariableValues::RecomputeBasicVariableValues() {
|
|
SCOPED_TIME_STAT(&stats_);
|
|
DCHECK(basis_factorization_.IsRefactorized());
|
|
const RowIndex num_rows = matrix_.num_rows();
|
|
scratchpad_.non_zeros.clear();
|
|
scratchpad_.values.AssignToZero(num_rows);
|
|
for (const ColIndex col : variables_info_.GetNotBasicBitRow()) {
|
|
const Fractional value = variable_values_[col];
|
|
matrix_.ColumnAddMultipleToDenseColumn(col, -value, &scratchpad_.values);
|
|
}
|
|
basis_factorization_.RightSolve(&scratchpad_);
|
|
for (RowIndex row(0); row < num_rows; ++row) {
|
|
variable_values_[basis_[row]] = scratchpad_[row];
|
|
}
|
|
|
|
// This makes sure that they will be recomputed if needed.
|
|
dual_prices_->Clear();
|
|
}
|
|
|
|
Fractional VariableValues::ComputeMaximumPrimalResidual() const {
|
|
SCOPED_TIME_STAT(&stats_);
|
|
scratchpad_.non_zeros.clear();
|
|
scratchpad_.values.AssignToZero(matrix_.num_rows());
|
|
const ColIndex num_cols = matrix_.num_cols();
|
|
for (ColIndex col(0); col < num_cols; ++col) {
|
|
const Fractional value = variable_values_[col];
|
|
matrix_.ColumnAddMultipleToDenseColumn(col, value, &scratchpad_.values);
|
|
}
|
|
return InfinityNorm(scratchpad_.values);
|
|
}
|
|
|
|
Fractional VariableValues::ComputeMaximumPrimalInfeasibility() const {
|
|
SCOPED_TIME_STAT(&stats_);
|
|
Fractional primal_infeasibility = 0.0;
|
|
const ColIndex num_cols = matrix_.num_cols();
|
|
for (ColIndex col(0); col < num_cols; ++col) {
|
|
const Fractional col_infeasibility = std::max(
|
|
GetUpperBoundInfeasibility(col), GetLowerBoundInfeasibility(col));
|
|
primal_infeasibility = std::max(primal_infeasibility, col_infeasibility);
|
|
}
|
|
return primal_infeasibility;
|
|
}
|
|
|
|
Fractional VariableValues::ComputeSumOfPrimalInfeasibilities() const {
|
|
SCOPED_TIME_STAT(&stats_);
|
|
Fractional sum = 0.0;
|
|
const ColIndex num_cols = matrix_.num_cols();
|
|
for (ColIndex col(0); col < num_cols; ++col) {
|
|
const Fractional col_infeasibility = std::max(
|
|
GetUpperBoundInfeasibility(col), GetLowerBoundInfeasibility(col));
|
|
sum += std::max(0.0, col_infeasibility);
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
void VariableValues::UpdateOnPivoting(const ScatteredColumn& direction,
|
|
ColIndex entering_col, Fractional step) {
|
|
SCOPED_TIME_STAT(&stats_);
|
|
DCHECK(IsFinite(step));
|
|
|
|
// Note(user): Some positions are ignored during the primal ratio test:
|
|
// - The rows for which direction_[row] < tolerance.
|
|
// - The non-zeros of direction_ignored_position_ in case of degeneracy.
|
|
// Such positions may result in basic variables going out of their bounds by
|
|
// more than the allowed tolerance. We could choose not to update these
|
|
// variables or not make them take out-of-bound values, but this would
|
|
// introduce artificial errors.
|
|
|
|
// Note that there is no need to call variables_info_.Update() on basic
|
|
// variables when they change values. Note also that the status of
|
|
// entering_col will be updated later.
|
|
for (const auto e : direction) {
|
|
const ColIndex col = basis_[e.row()];
|
|
variable_values_[col] -= e.coefficient() * step;
|
|
}
|
|
variable_values_[entering_col] += step;
|
|
}
|
|
|
|
void VariableValues::UpdateGivenNonBasicVariables(
|
|
const std::vector<ColIndex>& cols_to_update, bool update_basic_variables) {
|
|
SCOPED_TIME_STAT(&stats_);
|
|
if (!update_basic_variables) {
|
|
for (ColIndex col : cols_to_update) {
|
|
SetNonBasicVariableValueFromStatus(col);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const RowIndex num_rows = matrix_.num_rows();
|
|
initially_all_zero_scratchpad_.values.resize(num_rows, 0.0);
|
|
DCHECK(IsAllZero(initially_all_zero_scratchpad_.values));
|
|
initially_all_zero_scratchpad_.ClearSparseMask();
|
|
bool use_dense = false;
|
|
for (ColIndex col : cols_to_update) {
|
|
const Fractional old_value = variable_values_[col];
|
|
SetNonBasicVariableValueFromStatus(col);
|
|
if (use_dense) {
|
|
matrix_.ColumnAddMultipleToDenseColumn(
|
|
col, variable_values_[col] - old_value,
|
|
&initially_all_zero_scratchpad_.values);
|
|
} else {
|
|
matrix_.ColumnAddMultipleToSparseScatteredColumn(
|
|
col, variable_values_[col] - old_value,
|
|
&initially_all_zero_scratchpad_);
|
|
use_dense = initially_all_zero_scratchpad_.ShouldUseDenseIteration();
|
|
}
|
|
}
|
|
initially_all_zero_scratchpad_.ClearSparseMask();
|
|
initially_all_zero_scratchpad_.ClearNonZerosIfTooDense();
|
|
|
|
basis_factorization_.RightSolve(&initially_all_zero_scratchpad_);
|
|
if (initially_all_zero_scratchpad_.non_zeros.empty()) {
|
|
for (RowIndex row(0); row < num_rows; ++row) {
|
|
variable_values_[basis_[row]] -= initially_all_zero_scratchpad_[row];
|
|
}
|
|
initially_all_zero_scratchpad_.values.AssignToZero(num_rows);
|
|
RecomputeDualPrices();
|
|
return;
|
|
}
|
|
|
|
for (const auto e : initially_all_zero_scratchpad_) {
|
|
variable_values_[basis_[e.row()]] -= e.coefficient();
|
|
initially_all_zero_scratchpad_[e.row()] = 0.0;
|
|
}
|
|
UpdateDualPrices(initially_all_zero_scratchpad_.non_zeros);
|
|
initially_all_zero_scratchpad_.non_zeros.clear();
|
|
}
|
|
|
|
void VariableValues::RecomputeDualPrices() {
|
|
SCOPED_TIME_STAT(&stats_);
|
|
const RowIndex num_rows = matrix_.num_rows();
|
|
dual_prices_->ClearAndResize(num_rows);
|
|
dual_prices_->StartDenseUpdates();
|
|
|
|
const Fractional tolerance = parameters_.primal_feasibility_tolerance();
|
|
const DenseColumn& squared_norms = dual_edge_norms_->GetEdgeSquaredNorms();
|
|
for (RowIndex row(0); row < num_rows; ++row) {
|
|
const ColIndex col = basis_[row];
|
|
const Fractional infeasibility = std::max(GetUpperBoundInfeasibility(col),
|
|
GetLowerBoundInfeasibility(col));
|
|
if (infeasibility > tolerance) {
|
|
dual_prices_->DenseAddOrUpdate(
|
|
row, Square(infeasibility) / squared_norms[row]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VariableValues::UpdateDualPrices(const std::vector<RowIndex>& rows) {
|
|
if (dual_prices_->Size() != matrix_.num_rows()) {
|
|
RecomputeDualPrices();
|
|
return;
|
|
}
|
|
|
|
// Note(user): this is the same as the code in
|
|
// RecomputePrimalInfeasibilityInformation(), but we do need the clear part.
|
|
SCOPED_TIME_STAT(&stats_);
|
|
const Fractional tolerance = parameters_.primal_feasibility_tolerance();
|
|
const DenseColumn& squared_norms = dual_edge_norms_->GetEdgeSquaredNorms();
|
|
for (const RowIndex row : rows) {
|
|
const ColIndex col = basis_[row];
|
|
const Fractional infeasibility = std::max(GetUpperBoundInfeasibility(col),
|
|
GetLowerBoundInfeasibility(col));
|
|
if (infeasibility > tolerance) {
|
|
dual_prices_->AddOrUpdate(row,
|
|
Square(infeasibility) / squared_norms[row]);
|
|
} else {
|
|
dual_prices_->Remove(row);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace glop
|
|
} // namespace operations_research
|