// Copyright 2010-2018 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 CompactSparseMatrix& matrix, const RowToColMapping& basis, const VariablesInfo& variables_info, const BasisFactorization& basis_factorization) : matrix_(matrix), basis_(basis), variables_info_(variables_info), basis_factorization_(basis_factorization), 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: DCHECK_EQ(-kInfinity, lower_bounds[col]); DCHECK_EQ(kInfinity, upper_bounds[col]); variable_values_[col] = 0.0; 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& 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] = 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]; } } 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_); const DenseRow& lower_bounds = variables_info_.GetVariableLowerBounds(); const DenseRow& upper_bounds = variables_info_.GetVariableUpperBounds(); Fractional primal_infeasibility = 0.0; const ColIndex num_cols = matrix_.num_cols(); for (ColIndex col(0); col < num_cols; ++col) { const Fractional value = variable_values_[col]; primal_infeasibility = std::max( primal_infeasibility, std::max(lower_bounds[col] - value, value - upper_bounds[col])); } return primal_infeasibility; } Fractional VariableValues::ComputeSumOfPrimalInfeasibilities() const { SCOPED_TIME_STAT(&stats_); const DenseRow& lower_bounds = variables_info_.GetVariableLowerBounds(); const DenseRow& upper_bounds = variables_info_.GetVariableUpperBounds(); Fractional sum = 0.0; const ColIndex num_cols = matrix_.num_cols(); for (ColIndex col(0); col < num_cols; ++col) { const Fractional value = variable_values_[col]; sum += std::max( 0.0, std::max(lower_bounds[col] - value, value - upper_bounds[col])); } 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 theses // 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 RowIndex row : direction.non_zeros) { const ColIndex col = basis_[row]; variable_values_[col] -= direction[row] * step; } variable_values_[entering_col] += step; } void VariableValues::UpdateGivenNonBasicVariables( const std::vector& cols_to_update, bool update_basic_variables) { SCOPED_TIME_STAT(&stats_); if (update_basic_variables) { const RowIndex num_rows = matrix_.num_rows(); initially_all_zero_scratchpad_.values.resize(num_rows, 0.0); initially_all_zero_scratchpad_.is_non_zero.resize(num_rows, false); DCHECK(IsAllZero(initially_all_zero_scratchpad_.values)); DCHECK(IsAllFalse(initially_all_zero_scratchpad_.is_non_zero)); // TODO(user): Abort the non-zeros computation earlier if dense. for (ColIndex col : cols_to_update) { const Fractional old_value = variable_values_[col]; SetNonBasicVariableValueFromStatus(col); matrix_.ColumnAddMultipleToSparseScatteredColumn( col, variable_values_[col] - old_value, &initially_all_zero_scratchpad_); } if (ShouldUseDenseIteration(initially_all_zero_scratchpad_)) { initially_all_zero_scratchpad_.non_zeros.clear(); initially_all_zero_scratchpad_.is_non_zero.assign(num_rows, false); } else { for (const RowIndex row : initially_all_zero_scratchpad_.non_zeros) { initially_all_zero_scratchpad_.is_non_zero[row] = false; } } 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); ResetPrimalInfeasibilityInformation(); } else { for (const RowIndex row : initially_all_zero_scratchpad_.non_zeros) { variable_values_[basis_[row]] -= initially_all_zero_scratchpad_[row]; initially_all_zero_scratchpad_[row] = 0.0; } UpdatePrimalInfeasibilityInformation( initially_all_zero_scratchpad_.non_zeros); initially_all_zero_scratchpad_.non_zeros.clear(); } } else { for (ColIndex col : cols_to_update) { SetNonBasicVariableValueFromStatus(col); } } } const DenseColumn& VariableValues::GetPrimalSquaredInfeasibilities() const { return primal_squared_infeasibilities_; } const DenseBitColumn& VariableValues::GetPrimalInfeasiblePositions() const { return primal_infeasible_positions_; } void VariableValues::ResetPrimalInfeasibilityInformation() { SCOPED_TIME_STAT(&stats_); const RowIndex num_rows = matrix_.num_rows(); primal_squared_infeasibilities_.resize(num_rows, 0.0); primal_infeasible_positions_.ClearAndResize(num_rows); const DenseRow& lower_bounds = variables_info_.GetVariableLowerBounds(); const DenseRow& upper_bounds = variables_info_.GetVariableUpperBounds(); for (RowIndex row(0); row < num_rows; ++row) { const ColIndex col = basis_[row]; const Fractional value = variable_values_[col]; const Fractional magnitude = std::max(value - upper_bounds[col], lower_bounds[col] - value); if (magnitude > tolerance_) { primal_squared_infeasibilities_[row] = Square(magnitude); primal_infeasible_positions_.Set(row); } } } void VariableValues::UpdatePrimalInfeasibilityInformation( const std::vector& rows) { if (primal_squared_infeasibilities_.size() != matrix_.num_rows()) { ResetPrimalInfeasibilityInformation(); return; } // Note(user): this is the same as the code in // ResetPrimalInfeasibilityInformation(), but we do need the clear part. SCOPED_TIME_STAT(&stats_); const DenseRow& lower_bounds = variables_info_.GetVariableLowerBounds(); const DenseRow& upper_bounds = variables_info_.GetVariableUpperBounds(); for (const RowIndex row : rows) { const ColIndex col = basis_[row]; const Fractional value = variable_values_[col]; const Fractional magnitude = std::max(value - upper_bounds[col], lower_bounds[col] - value); if (magnitude > tolerance_) { primal_squared_infeasibilities_[row] = Square(magnitude); primal_infeasible_positions_.Set(row); } else { primal_infeasible_positions_.Clear(row); } } } } // namespace glop } // namespace operations_research