Files
ortools-clone/ortools/lp_data/lp_data.cc
Corentin Le Molgat c34026b101 Bump copyright to 2025
note: done using
```sh
git grep -l "2010-2024 Google" | xargs sed -i 's/2010-2024 Google/2010-2025 Google/'
```
2025-01-10 11:33:35 +01:00

1603 lines
60 KiB
C++

// Copyright 2010-2025 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/lp_data/lp_data.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/log/check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "ortools/base/logging.h"
#include "ortools/base/strong_vector.h"
#include "ortools/glop/parameters.pb.h"
#include "ortools/lp_data/lp_print_utils.h"
#include "ortools/lp_data/lp_types.h"
#include "ortools/lp_data/lp_utils.h"
#include "ortools/lp_data/matrix_utils.h"
#include "ortools/lp_data/permutation.h"
#include "ortools/lp_data/sparse.h"
#include "ortools/lp_data/sparse_column.h"
#include "ortools/util/fp_utils.h"
namespace operations_research {
namespace glop {
namespace {
// This should be the same as DCHECK(AreBoundsValid()), but the DCHECK() are
// split to give more meaningful information to the user in case of failure.
void DebugCheckBoundsValid(Fractional lower_bound, Fractional upper_bound) {
DCHECK(!std::isnan(lower_bound));
DCHECK(!std::isnan(upper_bound));
DCHECK(!(lower_bound == kInfinity && upper_bound == kInfinity));
DCHECK(!(lower_bound == -kInfinity && upper_bound == -kInfinity));
DCHECK_LE(lower_bound, upper_bound);
DCHECK(AreBoundsValid(lower_bound, upper_bound));
}
// Returns true if the bounds are the ones of a free or boxed row. Note that
// a fixed row is not counted as boxed.
bool AreBoundsFreeOrBoxed(Fractional lower_bound, Fractional upper_bound) {
if (lower_bound == -kInfinity && upper_bound == kInfinity) return true;
if (lower_bound != -kInfinity && upper_bound != kInfinity &&
lower_bound != upper_bound) {
return true;
}
return false;
}
template <class I, class T>
double Average(const util_intops::StrongVector<I, T>& v) {
const size_t size = v.size();
double sum = 0.0;
double n = 0.0; // n is used in a calculation involving doubles.
for (I i(0); i < size; ++i) {
if (v[i] == 0.0) continue;
++n;
sum += static_cast<double>(v[i].value());
}
return n == 0.0 ? 0.0 : sum / n;
}
template <class I, class T>
double StandardDeviation(const util_intops::StrongVector<I, T>& v) {
const size_t size = v.size();
double n = 0.0; // n is used in a calculation involving doubles.
double sigma_square = 0.0;
double sigma = 0.0;
for (I i(0); i < size; ++i) {
double sample = static_cast<double>(v[i].value());
if (sample == 0.0) continue;
sigma_square += sample * sample;
sigma += sample;
++n;
}
return n == 0.0 ? 0.0 : sqrt((sigma_square - sigma * sigma / n) / n);
}
// Returns 0 when the vector is empty.
template <class I, class T>
T GetMaxElement(const util_intops::StrongVector<I, T>& v) {
const size_t size = v.size();
if (size == 0) {
return T(0);
}
T max_index = v[I(0)];
for (I i(1); i < size; ++i) {
if (max_index < v[i]) {
max_index = v[i];
}
}
return max_index;
}
} // anonymous namespace
// --------------------------------------------------------
// LinearProgram
// --------------------------------------------------------
LinearProgram::LinearProgram()
: matrix_(),
transpose_matrix_(),
constraint_lower_bounds_(),
constraint_upper_bounds_(),
constraint_names_(),
objective_coefficients_(),
variable_lower_bounds_(),
variable_upper_bounds_(),
variable_names_(),
variable_types_(),
integer_variables_list_(),
variable_table_(),
constraint_table_(),
objective_offset_(0.0),
objective_scaling_factor_(1.0),
maximize_(false),
columns_are_known_to_be_clean_(true),
transpose_matrix_is_consistent_(true),
integer_variables_list_is_consistent_(true),
name_(),
first_slack_variable_(kInvalidCol) {}
void LinearProgram::Clear() {
matrix_.Clear();
transpose_matrix_.Clear();
constraint_lower_bounds_.clear();
constraint_upper_bounds_.clear();
constraint_names_.clear();
objective_coefficients_.clear();
variable_lower_bounds_.clear();
variable_upper_bounds_.clear();
variable_types_.clear();
integer_variables_list_.clear();
variable_names_.clear();
constraint_table_.clear();
variable_table_.clear();
maximize_ = false;
objective_offset_ = 0.0;
objective_scaling_factor_ = 1.0;
columns_are_known_to_be_clean_ = true;
transpose_matrix_is_consistent_ = true;
integer_variables_list_is_consistent_ = true;
name_.clear();
first_slack_variable_ = kInvalidCol;
}
ColIndex LinearProgram::CreateNewVariable() {
DCHECK_EQ(kInvalidCol, first_slack_variable_)
<< "New variables can't be added to programs that already have slack "
"variables. Consider calling LinearProgram::DeleteSlackVariables() "
"before adding new variables to the problem.";
objective_coefficients_.push_back(0.0);
variable_lower_bounds_.push_back(0);
variable_upper_bounds_.push_back(kInfinity);
variable_types_.push_back(VariableType::CONTINUOUS);
variable_names_.push_back("");
transpose_matrix_is_consistent_ = false;
return matrix_.AppendEmptyColumn();
}
ColIndex LinearProgram::CreateNewSlackVariable(bool is_integer_slack_variable,
Fractional lower_bound,
Fractional upper_bound,
const std::string& name) {
objective_coefficients_.push_back(0.0);
variable_lower_bounds_.push_back(lower_bound);
variable_upper_bounds_.push_back(upper_bound);
variable_types_.push_back(is_integer_slack_variable
? (VariableType::IMPLIED_INTEGER)
: (VariableType::CONTINUOUS));
variable_names_.push_back(name);
transpose_matrix_is_consistent_ = false;
return matrix_.AppendEmptyColumn();
}
RowIndex LinearProgram::CreateNewConstraint() {
DCHECK_EQ(kInvalidCol, first_slack_variable_)
<< "New constraints can't be added to programs that already have slack "
"variables. Consider calling LinearProgram::DeleteSlackVariables() "
"before adding new variables to the problem.";
const RowIndex row(constraint_names_.size());
matrix_.SetNumRows(row + 1);
constraint_lower_bounds_.push_back(Fractional(0.0));
constraint_upper_bounds_.push_back(Fractional(0.0));
constraint_names_.push_back("");
transpose_matrix_is_consistent_ = false;
return row;
}
ColIndex LinearProgram::FindOrCreateVariable(absl::string_view variable_id) {
const absl::flat_hash_map<std::string, ColIndex>::iterator it =
variable_table_.find(variable_id);
if (it != variable_table_.end()) {
return it->second;
} else {
const ColIndex col = CreateNewVariable();
variable_names_[col] = variable_id;
variable_table_[variable_id] = col;
return col;
}
}
RowIndex LinearProgram::FindOrCreateConstraint(
absl::string_view constraint_id) {
const absl::flat_hash_map<std::string, RowIndex>::iterator it =
constraint_table_.find(constraint_id);
if (it != constraint_table_.end()) {
return it->second;
} else {
const RowIndex row = CreateNewConstraint();
constraint_names_[row] = constraint_id;
constraint_table_[constraint_id] = row;
return row;
}
}
void LinearProgram::SetVariableName(ColIndex col, absl::string_view name) {
variable_names_[col] = std::string(name);
}
void LinearProgram::SetVariableType(ColIndex col, VariableType type) {
const bool var_was_integer = IsVariableInteger(col);
variable_types_[col] = type;
const bool var_is_integer = IsVariableInteger(col);
if (var_is_integer != var_was_integer) {
integer_variables_list_is_consistent_ = false;
}
}
void LinearProgram::SetConstraintName(RowIndex row, absl::string_view name) {
constraint_names_[row] = std::string(name);
}
void LinearProgram::SetVariableBounds(ColIndex col, Fractional lower_bound,
Fractional upper_bound) {
if (dcheck_bounds_) DebugCheckBoundsValid(lower_bound, upper_bound);
const bool var_was_binary = IsVariableBinary(col);
variable_lower_bounds_[col] = lower_bound;
variable_upper_bounds_[col] = upper_bound;
const bool var_is_binary = IsVariableBinary(col);
if (var_is_binary != var_was_binary) {
integer_variables_list_is_consistent_ = false;
}
}
void LinearProgram::UpdateAllIntegerVariableLists() const {
if (integer_variables_list_is_consistent_) return;
integer_variables_list_.clear();
binary_variables_list_.clear();
non_binary_variables_list_.clear();
const ColIndex num_cols = num_variables();
for (ColIndex col(0); col < num_cols; ++col) {
if (IsVariableInteger(col)) {
integer_variables_list_.push_back(col);
if (IsVariableBinary(col)) {
binary_variables_list_.push_back(col);
} else {
non_binary_variables_list_.push_back(col);
}
}
}
integer_variables_list_is_consistent_ = true;
}
const std::vector<ColIndex>& LinearProgram::IntegerVariablesList() const {
UpdateAllIntegerVariableLists();
return integer_variables_list_;
}
const std::vector<ColIndex>& LinearProgram::BinaryVariablesList() const {
UpdateAllIntegerVariableLists();
return binary_variables_list_;
}
const std::vector<ColIndex>& LinearProgram::NonBinaryVariablesList() const {
UpdateAllIntegerVariableLists();
return non_binary_variables_list_;
}
bool LinearProgram::IsVariableInteger(ColIndex col) const {
return variable_types_[col] == VariableType::INTEGER ||
variable_types_[col] == VariableType::IMPLIED_INTEGER;
}
bool LinearProgram::IsVariableBinary(ColIndex col) const {
// TODO(user): bounds of binary variables (and of integer ones) should
// be integer. Add a preprocessor for that.
return IsVariableInteger(col) && (variable_lower_bounds_[col] < kEpsilon) &&
(variable_lower_bounds_[col] > Fractional(-1)) &&
(variable_upper_bounds_[col] > Fractional(1) - kEpsilon) &&
(variable_upper_bounds_[col] < 2);
}
void LinearProgram::SetConstraintBounds(RowIndex row, Fractional lower_bound,
Fractional upper_bound) {
if (dcheck_bounds_) DebugCheckBoundsValid(lower_bound, upper_bound);
ResizeRowsIfNeeded(row);
constraint_lower_bounds_[row] = lower_bound;
constraint_upper_bounds_[row] = upper_bound;
}
void LinearProgram::SetCoefficient(RowIndex row, ColIndex col,
Fractional value) {
DCHECK(IsFinite(value));
ResizeRowsIfNeeded(row);
columns_are_known_to_be_clean_ = false;
transpose_matrix_is_consistent_ = false;
matrix_.mutable_column(col)->SetCoefficient(row, value);
}
void LinearProgram::SetObjectiveCoefficient(ColIndex col, Fractional value) {
DCHECK(IsFinite(value));
objective_coefficients_[col] = value;
}
void LinearProgram::SetObjectiveOffset(Fractional objective_offset) {
DCHECK(IsFinite(objective_offset));
objective_offset_ = objective_offset;
}
void LinearProgram::SetObjectiveScalingFactor(
Fractional objective_scaling_factor) {
DCHECK(IsFinite(objective_scaling_factor));
DCHECK_NE(0.0, objective_scaling_factor);
objective_scaling_factor_ = objective_scaling_factor;
}
void LinearProgram::SetMaximizationProblem(bool maximize) {
maximize_ = maximize;
}
void LinearProgram::CleanUp() {
if (columns_are_known_to_be_clean_) return;
matrix_.CleanUp();
columns_are_known_to_be_clean_ = true;
transpose_matrix_is_consistent_ = false;
}
bool LinearProgram::IsCleanedUp() const {
if (columns_are_known_to_be_clean_) return true;
columns_are_known_to_be_clean_ = matrix_.IsCleanedUp();
return columns_are_known_to_be_clean_;
}
std::string LinearProgram::GetVariableName(ColIndex col) const {
return col >= variable_names_.size() || variable_names_[col].empty()
? absl::StrFormat("c%d", col.value())
: variable_names_[col];
}
std::string LinearProgram::GetConstraintName(RowIndex row) const {
return row >= constraint_names_.size() || constraint_names_[row].empty()
? absl::StrFormat("r%d", row.value())
: constraint_names_[row];
}
LinearProgram::VariableType LinearProgram::GetVariableType(ColIndex col) const {
return variable_types_[col];
}
const SparseMatrix& LinearProgram::GetTransposeSparseMatrix() const {
if (!transpose_matrix_is_consistent_) {
transpose_matrix_.PopulateFromTranspose(matrix_);
transpose_matrix_is_consistent_ = true;
}
DCHECK_EQ(transpose_matrix_.num_rows().value(), matrix_.num_cols().value());
DCHECK_EQ(transpose_matrix_.num_cols().value(), matrix_.num_rows().value());
return transpose_matrix_;
}
SparseMatrix* LinearProgram::GetMutableTransposeSparseMatrix() {
if (!transpose_matrix_is_consistent_) {
transpose_matrix_.PopulateFromTranspose(matrix_);
}
// This enables a client to start modifying the matrix and then abort and not
// call UseTransposeMatrixAsReference(). Then, the other client of
// GetTransposeSparseMatrix() will still see the correct matrix.
transpose_matrix_is_consistent_ = false;
return &transpose_matrix_;
}
void LinearProgram::UseTransposeMatrixAsReference() {
DCHECK_EQ(transpose_matrix_.num_rows().value(), matrix_.num_cols().value());
DCHECK_EQ(transpose_matrix_.num_cols().value(), matrix_.num_rows().value());
matrix_.PopulateFromTranspose(transpose_matrix_);
transpose_matrix_is_consistent_ = true;
}
void LinearProgram::ClearTransposeMatrix() {
transpose_matrix_.Clear();
transpose_matrix_is_consistent_ = false;
}
const SparseColumn& LinearProgram::GetSparseColumn(ColIndex col) const {
return matrix_.column(col);
}
SparseColumn* LinearProgram::GetMutableSparseColumn(ColIndex col) {
columns_are_known_to_be_clean_ = false;
transpose_matrix_is_consistent_ = false;
return matrix_.mutable_column(col);
}
Fractional LinearProgram::GetObjectiveCoefficientForMinimizationVersion(
ColIndex col) const {
return maximize_ ? -objective_coefficients()[col]
: objective_coefficients()[col];
}
std::string LinearProgram::GetDimensionString() const {
Fractional min_magnitude = 0.0;
Fractional max_magnitude = 0.0;
matrix_.ComputeMinAndMaxMagnitudes(&min_magnitude, &max_magnitude);
return absl::StrFormat(
"%d rows, %d columns, %d entries with magnitude in [%e, %e]",
num_constraints().value(), num_variables().value(),
// static_cast<int64_t> is needed because the Android port uses int32_t.
static_cast<int64_t>(num_entries().value()), min_magnitude,
max_magnitude);
}
namespace {
template <typename FractionalValues>
void UpdateStats(const FractionalValues& values, int64_t* num_non_zeros,
Fractional* min_value, Fractional* max_value) {
for (const Fractional v : values) {
if (v == 0 || v == kInfinity || v == -kInfinity) continue;
*min_value = std::min(*min_value, v);
*max_value = std::max(*max_value, v);
++(*num_non_zeros);
}
}
} // namespace
std::string LinearProgram::GetObjectiveStatsString() const {
int64_t num_non_zeros = 0;
Fractional min_value = +kInfinity;
Fractional max_value = -kInfinity;
UpdateStats(objective_coefficients_, &num_non_zeros, &min_value, &max_value);
if (num_non_zeros == 0) {
return "No objective term. This is a pure feasibility problem.";
} else {
return absl::StrFormat("%d non-zeros, range [%e, %e]", num_non_zeros,
min_value, max_value);
}
}
std::string LinearProgram::GetBoundsStatsString() const {
int64_t num_non_zeros = 0;
Fractional min_value = +kInfinity;
Fractional max_value = -kInfinity;
UpdateStats(variable_lower_bounds_, &num_non_zeros, &min_value, &max_value);
UpdateStats(variable_upper_bounds_, &num_non_zeros, &min_value, &max_value);
UpdateStats(constraint_lower_bounds_, &num_non_zeros, &min_value, &max_value);
UpdateStats(constraint_upper_bounds_, &num_non_zeros, &min_value, &max_value);
if (num_non_zeros == 0) {
return "All variables/constraints bounds are zero or +/- infinity.";
} else {
return absl::StrFormat("%d non-zeros, range [%e, %e]", num_non_zeros,
min_value, max_value);
}
}
bool LinearProgram::SolutionIsWithinVariableBounds(
const DenseRow& solution, Fractional absolute_tolerance) const {
DCHECK_EQ(solution.size(), num_variables());
if (solution.size() != num_variables()) return false;
const ColIndex num_cols = num_variables();
for (ColIndex col = ColIndex(0); col < num_cols; ++col) {
if (!IsFinite(solution[col])) return false;
const Fractional lb_error = variable_lower_bounds()[col] - solution[col];
const Fractional ub_error = solution[col] - variable_upper_bounds()[col];
if (lb_error > absolute_tolerance || ub_error > absolute_tolerance) {
return false;
}
}
return true;
}
bool LinearProgram::SolutionIsLPFeasible(const DenseRow& solution,
Fractional absolute_tolerance) const {
if (!SolutionIsWithinVariableBounds(solution, absolute_tolerance)) {
return false;
}
const SparseMatrix& transpose = GetTransposeSparseMatrix();
const RowIndex num_rows = num_constraints();
for (RowIndex row = RowIndex(0); row < num_rows; ++row) {
const Fractional sum =
ScalarProduct(solution, transpose.column(RowToColIndex(row)));
if (!IsFinite(sum)) return false;
const Fractional lb_error = constraint_lower_bounds()[row] - sum;
const Fractional ub_error = sum - constraint_upper_bounds()[row];
if (lb_error > absolute_tolerance || ub_error > absolute_tolerance) {
return false;
}
}
return true;
}
bool LinearProgram::SolutionIsInteger(const DenseRow& solution,
Fractional absolute_tolerance) const {
DCHECK_EQ(solution.size(), num_variables());
if (solution.size() != num_variables()) return false;
for (ColIndex col : IntegerVariablesList()) {
if (!IsFinite(solution[col])) return false;
const Fractional fractionality = fabs(solution[col] - round(solution[col]));
if (fractionality > absolute_tolerance) return false;
}
return true;
}
bool LinearProgram::SolutionIsMIPFeasible(const DenseRow& solution,
Fractional absolute_tolerance) const {
return SolutionIsLPFeasible(solution, absolute_tolerance) &&
SolutionIsInteger(solution, absolute_tolerance);
}
void LinearProgram::ComputeSlackVariableValues(DenseRow* solution) const {
CHECK(solution != nullptr);
const ColIndex num_cols = GetFirstSlackVariable();
const SparseMatrix& transpose = GetTransposeSparseMatrix();
const RowIndex num_rows = num_constraints();
CHECK_EQ(solution->size(), num_variables());
for (RowIndex row = RowIndex(0); row < num_rows; ++row) {
const Fractional sum = PartialScalarProduct(
*solution, transpose.column(RowToColIndex(row)), num_cols.value());
const ColIndex slack_variable = GetSlackVariable(row);
CHECK_NE(slack_variable, kInvalidCol);
(*solution)[slack_variable] = -sum;
}
}
Fractional LinearProgram::ApplyObjectiveScalingAndOffset(
Fractional value) const {
return objective_scaling_factor() * (value + objective_offset());
}
Fractional LinearProgram::RemoveObjectiveScalingAndOffset(
Fractional value) const {
return value / objective_scaling_factor() - objective_offset();
}
std::string LinearProgram::Dump() const {
// Objective line.
std::string output = maximize_ ? "max:" : "min:";
if (objective_offset_ != 0.0) {
output += Stringify(objective_offset_);
}
const ColIndex num_cols = num_variables();
for (ColIndex col(0); col < num_cols; ++col) {
const Fractional coeff = objective_coefficients()[col];
if (coeff != 0.0) {
output += StringifyMonomial(coeff, GetVariableName(col), false);
}
}
output.append(";\n");
// Constraints.
const RowIndex num_rows = num_constraints();
const SparseMatrix& transpose = GetTransposeSparseMatrix();
for (RowIndex row(0); row < num_rows; ++row) {
const Fractional lower_bound = constraint_lower_bounds()[row];
const Fractional upper_bound = constraint_upper_bounds()[row];
output += GetConstraintName(row);
output += ":";
if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
output += " ";
output += Stringify(lower_bound);
output += " <=";
}
for (const auto& entry : transpose.column(RowToColIndex(row))) {
const Fractional coeff = entry.coefficient();
output += StringifyMonomial(
coeff, GetVariableName(RowToColIndex(entry.row())), false);
}
if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
output += " <= ";
output += Stringify(upper_bound);
} else if (lower_bound == upper_bound) {
output += " = ";
output += Stringify(upper_bound);
} else if (lower_bound != -kInfinity) {
output += " >= ";
output += Stringify(lower_bound);
} else if (lower_bound != kInfinity) {
output += " <= ";
output += Stringify(upper_bound);
}
output += ";\n";
}
// Variables.
for (ColIndex col(0); col < num_cols; ++col) {
const Fractional lower_bound = variable_lower_bounds()[col];
const Fractional upper_bound = variable_upper_bounds()[col];
if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
output += Stringify(lower_bound);
output += " <= ";
}
output += GetVariableName(col);
if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
output += " <= ";
output += Stringify(upper_bound);
} else if (lower_bound == upper_bound) {
output += " = ";
output += Stringify(upper_bound);
} else if (lower_bound != -kInfinity) {
output += " >= ";
output += Stringify(lower_bound);
} else if (lower_bound != kInfinity) {
output += " <= ";
output += Stringify(upper_bound);
}
output += ";\n";
}
// Integer variables.
// TODO(user): if needed provide similar output for binary variables.
const std::vector<ColIndex>& integer_variables = IntegerVariablesList();
if (!integer_variables.empty()) {
output += "int";
for (ColIndex col : integer_variables) {
output += " ";
output += GetVariableName(col);
}
output += ";\n";
}
return output;
}
std::string LinearProgram::DumpSolution(const DenseRow& variable_values) const {
DCHECK_EQ(variable_values.size(), num_variables());
std::string output;
for (ColIndex col(0); col < variable_values.size(); ++col) {
if (!output.empty()) absl::StrAppend(&output, ", ");
absl::StrAppend(&output, GetVariableName(col), " = ",
(variable_values[col]));
}
return output;
}
std::string LinearProgram::GetProblemStats() const {
return ProblemStatFormatter(
"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,"
"%d,%d,%d,%d");
}
std::string LinearProgram::GetPrettyProblemStats() const {
return ProblemStatFormatter(
"Number of rows : %d\n"
"Number of variables in file : %d\n"
"Number of entries (non-zeros) : %d\n"
"Number of entries in the objective : %d\n"
"Number of entries in the right-hand side : %d\n"
"Number of <= constraints : %d\n"
"Number of >= constraints : %d\n"
"Number of = constraints : %d\n"
"Number of range constraints : %d\n"
"Number of non-negative variables : %d\n"
"Number of boxed variables : %d\n"
"Number of free variables : %d\n"
"Number of fixed variables : %d\n"
"Number of other variables : %d\n"
"Number of integer variables : %d\n"
"Number of binary variables : %d\n"
"Number of non-binary integer variables : %d\n"
"Number of continuous variables : %d\n");
}
std::string LinearProgram::GetNonZeroStats() const {
return NonZeroStatFormatter("%.2f%%,%d,%.2f,%.2f,%d,%.2f,%.2f");
}
std::string LinearProgram::GetPrettyNonZeroStats() const {
return NonZeroStatFormatter(
"Fill rate : %.2f%%\n"
"Entries in row (Max / average / std. dev.) : %d / %.2f / %.2f\n"
"Entries in column (Max / average / std. dev.): %d / %.2f / %.2f\n");
}
void LinearProgram::AddSlackVariablesWhereNecessary(
bool detect_integer_constraints) {
// Clean up the matrix. We're going to add entries, but we'll only be adding
// them to new columns, and only one entry per column, which does not
// invalidate the "cleanness" of the matrix.
CleanUp();
// Detect which constraints produce an integer slack variable. A constraint
// has an integer slack variable, if it contains only integer variables with
// integer coefficients. We do not check the bounds of the constraints,
// because in such case, they will be tightened to integer values by the
// preprocessors.
//
// We don't use the transpose, because it might not be valid and it would be
// inefficient to update it and invalidate it again at the end of this
// preprocessor.
DenseBooleanColumn has_integer_slack_variable(num_constraints(),
detect_integer_constraints);
if (detect_integer_constraints) {
for (ColIndex col(0); col < num_variables(); ++col) {
const SparseColumn& column = matrix_.column(col);
const bool is_integer_variable = IsVariableInteger(col);
for (const SparseColumn::Entry& entry : column) {
const RowIndex row = entry.row();
has_integer_slack_variable[row] =
has_integer_slack_variable[row] && is_integer_variable &&
round(entry.coefficient()) == entry.coefficient();
}
}
}
// Extend the matrix of the problem with an identity matrix.
const ColIndex original_num_variables = num_variables();
for (RowIndex row(0); row < num_constraints(); ++row) {
ColIndex slack_variable_index = GetSlackVariable(row);
if (slack_variable_index != kInvalidCol &&
slack_variable_index < original_num_variables) {
// Slack variable is already present in this constraint.
continue;
}
const ColIndex slack_col = CreateNewSlackVariable(
has_integer_slack_variable[row], -constraint_upper_bounds_[row],
-constraint_lower_bounds_[row], absl::StrCat("s", row.value()));
SetCoefficient(row, slack_col, 1.0);
SetConstraintBounds(row, 0.0, 0.0);
}
columns_are_known_to_be_clean_ = true;
transpose_matrix_is_consistent_ = false;
if (first_slack_variable_ == kInvalidCol) {
first_slack_variable_ = original_num_variables;
}
}
ColIndex LinearProgram::GetFirstSlackVariable() const {
return first_slack_variable_;
}
ColIndex LinearProgram::GetSlackVariable(RowIndex row) const {
DCHECK_GE(row, RowIndex(0));
DCHECK_LT(row, num_constraints());
if (first_slack_variable_ == kInvalidCol) {
return kInvalidCol;
}
return first_slack_variable_ + RowToColIndex(row);
}
void LinearProgram::PopulateFromDual(const LinearProgram& dual,
RowToColMapping* duplicated_rows) {
const ColIndex dual_num_variables = dual.num_variables();
const RowIndex dual_num_constraints = dual.num_constraints();
Clear();
// We always take the dual in its minimization form thanks to the
// GetObjectiveCoefficientForMinimizationVersion() below, so this will always
// be a maximization problem.
SetMaximizationProblem(true);
// Taking the dual does not change the offset nor the objective scaling
// factor.
SetObjectiveOffset(dual.objective_offset());
SetObjectiveScalingFactor(dual.objective_scaling_factor());
// Create the dual variables y, with bounds depending on the type
// of constraints in the primal.
for (RowIndex dual_row(0); dual_row < dual_num_constraints; ++dual_row) {
CreateNewVariable();
const ColIndex col = RowToColIndex(dual_row);
const Fractional lower_bound = dual.constraint_lower_bounds()[dual_row];
const Fractional upper_bound = dual.constraint_upper_bounds()[dual_row];
if (lower_bound == upper_bound) {
SetVariableBounds(col, -kInfinity, kInfinity);
SetObjectiveCoefficient(col, lower_bound);
} else if (upper_bound != kInfinity) {
// Note that for a ranged constraint, the loop will be continued here.
// This is wanted because we want to deal with the lower_bound afterwards.
SetVariableBounds(col, -kInfinity, 0.0);
SetObjectiveCoefficient(col, upper_bound);
} else if (lower_bound != -kInfinity) {
SetVariableBounds(col, 0.0, kInfinity);
SetObjectiveCoefficient(col, lower_bound);
} else {
// This code does not support free rows in linear_program.
LOG(DFATAL) << "PopulateFromDual() was called with a program "
<< "containing free constraints.";
}
}
// Create the dual slack variables v.
for (ColIndex dual_col(0); dual_col < dual_num_variables; ++dual_col) {
const Fractional lower_bound = dual.variable_lower_bounds()[dual_col];
if (lower_bound != -kInfinity) {
const ColIndex col = CreateNewVariable();
SetObjectiveCoefficient(col, lower_bound);
SetVariableBounds(col, 0.0, kInfinity);
const RowIndex row = ColToRowIndex(dual_col);
SetCoefficient(row, col, Fractional(1.0));
}
}
// Create the dual surplus variables w.
for (ColIndex dual_col(0); dual_col < dual_num_variables; ++dual_col) {
const Fractional upper_bound = dual.variable_upper_bounds()[dual_col];
if (upper_bound != kInfinity) {
const ColIndex col = CreateNewVariable();
SetObjectiveCoefficient(col, upper_bound);
SetVariableBounds(col, -kInfinity, 0.0);
const RowIndex row = ColToRowIndex(dual_col);
SetCoefficient(row, col, Fractional(1.0));
}
}
// Store the transpose of the matrix.
for (ColIndex dual_col(0); dual_col < dual_num_variables; ++dual_col) {
const RowIndex row = ColToRowIndex(dual_col);
const Fractional row_bound =
dual.GetObjectiveCoefficientForMinimizationVersion(dual_col);
SetConstraintBounds(row, row_bound, row_bound);
for (const SparseColumn::Entry e : dual.GetSparseColumn(dual_col)) {
const RowIndex dual_row = e.row();
const ColIndex col = RowToColIndex(dual_row);
SetCoefficient(row, col, e.coefficient());
}
}
// Take care of ranged constraints.
duplicated_rows->assign(dual_num_constraints, kInvalidCol);
for (RowIndex dual_row(0); dual_row < dual_num_constraints; ++dual_row) {
const Fractional lower_bound = dual.constraint_lower_bounds()[dual_row];
const Fractional upper_bound = dual.constraint_upper_bounds()[dual_row];
if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
DCHECK(upper_bound != kInfinity || lower_bound != -kInfinity);
// upper_bound was done in a loop above, now do the lower_bound.
const ColIndex col = CreateNewVariable();
SetVariableBounds(col, 0.0, kInfinity);
SetObjectiveCoefficient(col, lower_bound);
matrix_.mutable_column(col)->PopulateFromSparseVector(
matrix_.column(RowToColIndex(dual_row)));
(*duplicated_rows)[dual_row] = col;
}
}
// We know that the columns are ordered by rows.
columns_are_known_to_be_clean_ = true;
transpose_matrix_is_consistent_ = false;
}
void LinearProgram::PopulateFromLinearProgram(
const LinearProgram& linear_program) {
matrix_.PopulateFromSparseMatrix(linear_program.matrix_);
if (linear_program.transpose_matrix_is_consistent_) {
transpose_matrix_is_consistent_ = true;
transpose_matrix_.PopulateFromSparseMatrix(
linear_program.transpose_matrix_);
} else {
transpose_matrix_is_consistent_ = false;
transpose_matrix_.Clear();
}
constraint_lower_bounds_ = linear_program.constraint_lower_bounds_;
constraint_upper_bounds_ = linear_program.constraint_upper_bounds_;
constraint_names_ = linear_program.constraint_names_;
constraint_table_.clear();
PopulateNameObjectiveAndVariablesFromLinearProgram(linear_program);
first_slack_variable_ = linear_program.first_slack_variable_;
}
void LinearProgram::PopulateFromPermutedLinearProgram(
const LinearProgram& lp, const RowPermutation& row_permutation,
const ColumnPermutation& col_permutation) {
DCHECK(lp.IsCleanedUp());
DCHECK_EQ(row_permutation.size(), lp.num_constraints());
DCHECK_EQ(col_permutation.size(), lp.num_variables());
DCHECK_EQ(lp.GetFirstSlackVariable(), kInvalidCol);
Clear();
// Populate matrix coefficients.
ColumnPermutation inverse_col_permutation;
inverse_col_permutation.PopulateFromInverse(col_permutation);
matrix_.PopulateFromPermutedMatrix(lp.matrix_, row_permutation,
inverse_col_permutation);
ClearTransposeMatrix();
// Populate constraints.
ApplyPermutation(row_permutation, lp.constraint_lower_bounds(),
&constraint_lower_bounds_);
ApplyPermutation(row_permutation, lp.constraint_upper_bounds(),
&constraint_upper_bounds_);
// Populate variables.
ApplyPermutation(col_permutation, lp.objective_coefficients(),
&objective_coefficients_);
ApplyPermutation(col_permutation, lp.variable_lower_bounds(),
&variable_lower_bounds_);
ApplyPermutation(col_permutation, lp.variable_upper_bounds(),
&variable_upper_bounds_);
ApplyPermutation(col_permutation, lp.variable_types(), &variable_types_);
integer_variables_list_is_consistent_ = false;
// There is no vector based accessor to names, because they may be created
// on the fly.
constraint_names_.resize(lp.num_constraints());
for (RowIndex old_row(0); old_row < lp.num_constraints(); ++old_row) {
const RowIndex new_row = row_permutation[old_row];
constraint_names_[new_row] = lp.constraint_names_[old_row];
}
variable_names_.resize(lp.num_variables());
for (ColIndex old_col(0); old_col < lp.num_variables(); ++old_col) {
const ColIndex new_col = col_permutation[old_col];
variable_names_[new_col] = lp.variable_names_[old_col];
}
// Populate singular fields.
maximize_ = lp.maximize_;
objective_offset_ = lp.objective_offset_;
objective_scaling_factor_ = lp.objective_scaling_factor_;
name_ = lp.name_;
}
void LinearProgram::PopulateFromLinearProgramVariables(
const LinearProgram& linear_program) {
matrix_.PopulateFromZero(RowIndex(0), linear_program.num_variables());
first_slack_variable_ = kInvalidCol;
transpose_matrix_is_consistent_ = false;
transpose_matrix_.Clear();
constraint_lower_bounds_.clear();
constraint_upper_bounds_.clear();
constraint_names_.clear();
constraint_table_.clear();
PopulateNameObjectiveAndVariablesFromLinearProgram(linear_program);
}
void LinearProgram::PopulateNameObjectiveAndVariablesFromLinearProgram(
const LinearProgram& linear_program) {
objective_coefficients_ = linear_program.objective_coefficients_;
variable_lower_bounds_ = linear_program.variable_lower_bounds_;
variable_upper_bounds_ = linear_program.variable_upper_bounds_;
variable_names_ = linear_program.variable_names_;
variable_types_ = linear_program.variable_types_;
integer_variables_list_is_consistent_ =
linear_program.integer_variables_list_is_consistent_;
integer_variables_list_ = linear_program.integer_variables_list_;
binary_variables_list_ = linear_program.binary_variables_list_;
non_binary_variables_list_ = linear_program.non_binary_variables_list_;
variable_table_.clear();
maximize_ = linear_program.maximize_;
objective_offset_ = linear_program.objective_offset_;
objective_scaling_factor_ = linear_program.objective_scaling_factor_;
columns_are_known_to_be_clean_ =
linear_program.columns_are_known_to_be_clean_;
name_ = linear_program.name_;
}
void LinearProgram::AddConstraints(
const SparseMatrix& coefficients, const DenseColumn& left_hand_sides,
const DenseColumn& right_hand_sides,
const StrictITIVector<RowIndex, std::string>& names) {
const RowIndex num_new_constraints = coefficients.num_rows();
DCHECK_EQ(num_variables(), coefficients.num_cols());
DCHECK_EQ(num_new_constraints, left_hand_sides.size());
DCHECK_EQ(num_new_constraints, right_hand_sides.size());
DCHECK_EQ(num_new_constraints, names.size());
matrix_.AppendRowsFromSparseMatrix(coefficients);
transpose_matrix_is_consistent_ = false;
transpose_matrix_.Clear();
columns_are_known_to_be_clean_ = false;
// Copy constraint bounds and names from linear_program.
constraint_lower_bounds_.insert(constraint_lower_bounds_.end(),
left_hand_sides.begin(),
left_hand_sides.end());
constraint_upper_bounds_.insert(constraint_upper_bounds_.end(),
right_hand_sides.begin(),
right_hand_sides.end());
constraint_names_.insert(constraint_names_.end(), names.begin(), names.end());
}
void LinearProgram::AddConstraintsWithSlackVariables(
const SparseMatrix& coefficients, const DenseColumn& left_hand_sides,
const DenseColumn& right_hand_sides,
const StrictITIVector<RowIndex, std::string>& names,
bool detect_integer_constraints_for_slack) {
AddConstraints(coefficients, left_hand_sides, right_hand_sides, names);
AddSlackVariablesWhereNecessary(detect_integer_constraints_for_slack);
}
bool LinearProgram::UpdateVariableBoundsToIntersection(
const DenseRow& variable_lower_bounds,
const DenseRow& variable_upper_bounds) {
const ColIndex num_vars = num_variables();
DCHECK_EQ(variable_lower_bounds.size(), num_vars);
DCHECK_EQ(variable_upper_bounds.size(), num_vars);
DenseRow new_lower_bounds(num_vars, 0);
DenseRow new_upper_bounds(num_vars, 0);
for (ColIndex i(0); i < num_vars; ++i) {
const Fractional new_lower_bound =
std::max(variable_lower_bounds[i], variable_lower_bounds_[i]);
const Fractional new_upper_bound =
std::min(variable_upper_bounds[i], variable_upper_bounds_[i]);
if (new_lower_bound > new_upper_bound) {
return false;
}
new_lower_bounds[i] = new_lower_bound;
new_upper_bounds[i] = new_upper_bound;
}
variable_lower_bounds_.swap(new_lower_bounds);
variable_upper_bounds_.swap(new_upper_bounds);
return true;
}
void LinearProgram::Swap(LinearProgram* linear_program) {
matrix_.Swap(&linear_program->matrix_);
transpose_matrix_.Swap(&linear_program->transpose_matrix_);
constraint_lower_bounds_.swap(linear_program->constraint_lower_bounds_);
constraint_upper_bounds_.swap(linear_program->constraint_upper_bounds_);
constraint_names_.swap(linear_program->constraint_names_);
objective_coefficients_.swap(linear_program->objective_coefficients_);
variable_lower_bounds_.swap(linear_program->variable_lower_bounds_);
variable_upper_bounds_.swap(linear_program->variable_upper_bounds_);
variable_names_.swap(linear_program->variable_names_);
variable_types_.swap(linear_program->variable_types_);
integer_variables_list_.swap(linear_program->integer_variables_list_);
binary_variables_list_.swap(linear_program->binary_variables_list_);
non_binary_variables_list_.swap(linear_program->non_binary_variables_list_);
variable_table_.swap(linear_program->variable_table_);
constraint_table_.swap(linear_program->constraint_table_);
std::swap(maximize_, linear_program->maximize_);
std::swap(objective_offset_, linear_program->objective_offset_);
std::swap(objective_scaling_factor_,
linear_program->objective_scaling_factor_);
std::swap(columns_are_known_to_be_clean_,
linear_program->columns_are_known_to_be_clean_);
std::swap(transpose_matrix_is_consistent_,
linear_program->transpose_matrix_is_consistent_);
std::swap(integer_variables_list_is_consistent_,
linear_program->integer_variables_list_is_consistent_);
name_.swap(linear_program->name_);
std::swap(first_slack_variable_, linear_program->first_slack_variable_);
}
void LinearProgram::DeleteColumns(const DenseBooleanRow& columns_to_delete) {
if (columns_to_delete.empty()) return;
integer_variables_list_is_consistent_ = false;
const ColIndex num_cols = num_variables();
ColumnPermutation permutation(num_cols);
ColIndex new_index(0);
for (ColIndex col(0); col < num_cols; ++col) {
permutation[col] = new_index;
if (col >= columns_to_delete.size() || !columns_to_delete[col]) {
objective_coefficients_[new_index] = objective_coefficients_[col];
variable_lower_bounds_[new_index] = variable_lower_bounds_[col];
variable_upper_bounds_[new_index] = variable_upper_bounds_[col];
variable_names_[new_index] = variable_names_[col];
variable_types_[new_index] = variable_types_[col];
++new_index;
} else {
permutation[col] = kInvalidCol;
}
}
matrix_.DeleteColumns(columns_to_delete);
objective_coefficients_.resize(new_index, 0.0);
variable_lower_bounds_.resize(new_index, 0.0);
variable_upper_bounds_.resize(new_index, 0.0);
variable_types_.resize(new_index, VariableType::CONTINUOUS);
variable_names_.resize(new_index, "");
// Remove the id of the deleted columns and adjust the index of the other.
absl::flat_hash_map<std::string, ColIndex>::iterator it =
variable_table_.begin();
while (it != variable_table_.end()) {
const ColIndex col = it->second;
if (col >= columns_to_delete.size() || !columns_to_delete[col]) {
it->second = permutation[col];
++it;
} else {
// This safely deletes the entry and moves the iterator one step ahead.
variable_table_.erase(it++);
}
}
// Eventually update transpose_matrix_.
if (transpose_matrix_is_consistent_) {
transpose_matrix_.DeleteRows(
ColToRowIndex(new_index),
reinterpret_cast<const RowPermutation&>(permutation));
}
}
void LinearProgram::DeleteSlackVariables() {
DCHECK_NE(first_slack_variable_, kInvalidCol);
DenseBooleanRow slack_variables(matrix_.num_cols(), false);
// Restore the bounds on the constraints corresponding to the slack variables.
for (ColIndex slack_variable = first_slack_variable_;
slack_variable < matrix_.num_cols(); ++slack_variable) {
const SparseColumn& column = matrix_.column(slack_variable);
// Slack variables appear only in the constraints for which they were
// created. We can find this constraint by looking at the (only) entry in
// the columnm of the slack variable.
DCHECK_EQ(column.num_entries(), 1);
const RowIndex row = column.EntryRow(EntryIndex(0));
DCHECK_EQ(constraint_lower_bounds_[row], 0.0);
DCHECK_EQ(constraint_upper_bounds_[row], 0.0);
SetConstraintBounds(row, -variable_upper_bounds_[slack_variable],
-variable_lower_bounds_[slack_variable]);
slack_variables[slack_variable] = true;
}
DeleteColumns(slack_variables);
first_slack_variable_ = kInvalidCol;
}
namespace {
// Note that we ignore zeros and infinities because they do not matter from a
// scaling perspective where this function is used.
template <typename FractionalRange>
void UpdateMinAndMaxMagnitude(const FractionalRange& range,
Fractional* min_magnitude,
Fractional* max_magnitude) {
for (const Fractional value : range) {
const Fractional magnitude = std::abs(value);
if (magnitude == 0 || magnitude == kInfinity) continue;
*min_magnitude = std::min(*min_magnitude, magnitude);
*max_magnitude = std::max(*max_magnitude, magnitude);
}
}
Fractional GetMedianScalingFactor(const DenseRow& range) {
std::vector<Fractional> median;
for (const Fractional value : range) {
if (value == 0.0) continue;
median.push_back(std::abs(value));
}
if (median.empty()) return 1.0;
std::sort(median.begin(), median.end());
return median[median.size() / 2];
}
Fractional GetMeanScalingFactor(const DenseRow& range) {
Fractional mean = 0.0;
int num_non_zeros = 0;
for (const Fractional value : range) {
if (value == 0.0) continue;
++num_non_zeros;
mean += std::abs(value);
}
if (num_non_zeros == 0.0) return 1.0;
return mean / static_cast<Fractional>(num_non_zeros);
}
Fractional ComputeDivisorSoThatRangeContainsOne(Fractional min_magnitude,
Fractional max_magnitude) {
if (min_magnitude > 1.0 && min_magnitude < kInfinity) {
return min_magnitude;
} else if (max_magnitude > 0.0 && max_magnitude < 1.0) {
return max_magnitude;
}
return 1.0;
}
} // namespace
Fractional LinearProgram::ScaleObjective(
GlopParameters::CostScalingAlgorithm method) {
Fractional min_magnitude = kInfinity;
Fractional max_magnitude = 0.0;
UpdateMinAndMaxMagnitude(objective_coefficients(), &min_magnitude,
&max_magnitude);
Fractional cost_scaling_factor = 1.0;
switch (method) {
case GlopParameters::NO_COST_SCALING:
break;
case GlopParameters::CONTAIN_ONE_COST_SCALING:
cost_scaling_factor =
ComputeDivisorSoThatRangeContainsOne(min_magnitude, max_magnitude);
break;
case GlopParameters::MEAN_COST_SCALING:
cost_scaling_factor = GetMeanScalingFactor(objective_coefficients());
break;
case GlopParameters::MEDIAN_COST_SCALING:
cost_scaling_factor = GetMedianScalingFactor(objective_coefficients());
break;
}
if (cost_scaling_factor != 1.0) {
for (ColIndex col(0); col < num_variables(); ++col) {
if (objective_coefficients()[col] == 0.0) continue;
SetObjectiveCoefficient(
col, objective_coefficients()[col] / cost_scaling_factor);
}
SetObjectiveScalingFactor(objective_scaling_factor() * cost_scaling_factor);
SetObjectiveOffset(objective_offset() / cost_scaling_factor);
}
VLOG(1) << "Objective magnitude range is [" << min_magnitude << ", "
<< max_magnitude << "] (dividing by " << cost_scaling_factor << ").";
return cost_scaling_factor;
}
Fractional LinearProgram::ScaleBounds() {
Fractional min_magnitude = kInfinity;
Fractional max_magnitude = 0.0;
UpdateMinAndMaxMagnitude(variable_lower_bounds(), &min_magnitude,
&max_magnitude);
UpdateMinAndMaxMagnitude(variable_upper_bounds(), &min_magnitude,
&max_magnitude);
UpdateMinAndMaxMagnitude(constraint_lower_bounds(), &min_magnitude,
&max_magnitude);
UpdateMinAndMaxMagnitude(constraint_upper_bounds(), &min_magnitude,
&max_magnitude);
const Fractional bound_scaling_factor =
ComputeDivisorSoThatRangeContainsOne(min_magnitude, max_magnitude);
if (bound_scaling_factor != 1.0) {
SetObjectiveScalingFactor(objective_scaling_factor() *
bound_scaling_factor);
SetObjectiveOffset(objective_offset() / bound_scaling_factor);
for (ColIndex col(0); col < num_variables(); ++col) {
SetVariableBounds(col,
variable_lower_bounds()[col] / bound_scaling_factor,
variable_upper_bounds()[col] / bound_scaling_factor);
}
for (RowIndex row(0); row < num_constraints(); ++row) {
SetConstraintBounds(
row, constraint_lower_bounds()[row] / bound_scaling_factor,
constraint_upper_bounds()[row] / bound_scaling_factor);
}
}
VLOG(1) << "Bounds magnitude range is [" << min_magnitude << ", "
<< max_magnitude << "] (dividing bounds by " << bound_scaling_factor
<< ").";
return bound_scaling_factor;
}
void LinearProgram::DeleteRows(const DenseBooleanColumn& rows_to_delete) {
if (rows_to_delete.empty()) return;
// Deal with row-indexed data and construct the row mapping that will need to
// be applied to every column entry.
const RowIndex num_rows = num_constraints();
RowPermutation permutation(num_rows);
RowIndex new_index(0);
for (RowIndex row(0); row < num_rows; ++row) {
if (row >= rows_to_delete.size() || !rows_to_delete[row]) {
constraint_lower_bounds_[new_index] = constraint_lower_bounds_[row];
constraint_upper_bounds_[new_index] = constraint_upper_bounds_[row];
constraint_names_[new_index].swap(constraint_names_[row]);
permutation[row] = new_index;
++new_index;
} else {
permutation[row] = kInvalidRow;
}
}
constraint_lower_bounds_.resize(new_index, 0.0);
constraint_upper_bounds_.resize(new_index, 0.0);
constraint_names_.resize(new_index, "");
// Remove the rows from the matrix.
matrix_.DeleteRows(new_index, permutation);
// Remove the id of the deleted rows and adjust the index of the other.
absl::flat_hash_map<std::string, RowIndex>::iterator it =
constraint_table_.begin();
while (it != constraint_table_.end()) {
const RowIndex row = it->second;
if (permutation[row] != kInvalidRow) {
it->second = permutation[row];
++it;
} else {
// This safely deletes the entry and moves the iterator one step ahead.
constraint_table_.erase(it++);
}
}
// Eventually update transpose_matrix_.
if (transpose_matrix_is_consistent_) {
transpose_matrix_.DeleteColumns(
reinterpret_cast<const DenseBooleanRow&>(rows_to_delete));
}
}
bool LinearProgram::IsValid(Fractional max_valid_magnitude) const {
if (!IsFinite(objective_offset_)) return false;
if (std::abs(objective_offset_) > max_valid_magnitude) return false;
if (!IsFinite(objective_scaling_factor_)) return false;
if (objective_scaling_factor_ == 0.0) return false;
if (std::abs(objective_scaling_factor_) > max_valid_magnitude) return false;
const ColIndex num_cols = num_variables();
for (ColIndex col(0); col < num_cols; ++col) {
const Fractional lb = variable_lower_bounds()[col];
const Fractional ub = variable_upper_bounds()[col];
if (!AreBoundsValid(lb, ub)) return false;
if (IsFinite(lb) && std::abs(lb) > max_valid_magnitude) return false;
if (IsFinite(ub) && std::abs(ub) > max_valid_magnitude) return false;
if (!IsFinite(objective_coefficients()[col])) {
return false;
}
if (std::abs(objective_coefficients()[col]) > max_valid_magnitude) {
return false;
}
for (const SparseColumn::Entry e : GetSparseColumn(col)) {
if (!IsFinite(e.coefficient())) return false;
if (std::abs(e.coefficient()) > max_valid_magnitude) return false;
}
}
if (constraint_upper_bounds_.size() != constraint_lower_bounds_.size()) {
return false;
}
for (RowIndex row(0); row < constraint_lower_bounds_.size(); ++row) {
const Fractional lb = constraint_lower_bounds()[row];
const Fractional ub = constraint_upper_bounds()[row];
if (!AreBoundsValid(lb, ub)) return false;
if (IsFinite(lb) && std::abs(lb) > max_valid_magnitude) return false;
if (IsFinite(ub) && std::abs(ub) > max_valid_magnitude) return false;
}
return true;
}
std::string LinearProgram::ProblemStatFormatter(
const absl::string_view format) const {
int num_objective_non_zeros = 0;
int num_non_negative_variables = 0;
int num_boxed_variables = 0;
int num_free_variables = 0;
int num_fixed_variables = 0;
int num_other_variables = 0;
const ColIndex num_cols = num_variables();
for (ColIndex col(0); col < num_cols; ++col) {
if (objective_coefficients()[col] != 0.0) {
++num_objective_non_zeros;
}
const Fractional lower_bound = variable_lower_bounds()[col];
const Fractional upper_bound = variable_upper_bounds()[col];
const bool lower_bounded = (lower_bound != -kInfinity);
const bool upper_bounded = (upper_bound != kInfinity);
if (!lower_bounded && !upper_bounded) {
++num_free_variables;
} else if (lower_bound == 0.0 && !upper_bounded) {
++num_non_negative_variables;
} else if (!upper_bounded || !lower_bounded) {
++num_other_variables;
} else if (lower_bound == upper_bound) {
++num_fixed_variables;
} else {
++num_boxed_variables;
}
}
int num_range_constraints = 0;
int num_less_than_constraints = 0;
int num_greater_than_constraints = 0;
int num_equal_constraints = 0;
int num_rhs_non_zeros = 0;
const RowIndex num_rows = num_constraints();
for (RowIndex row(0); row < num_rows; ++row) {
const Fractional lower_bound = constraint_lower_bounds()[row];
const Fractional upper_bound = constraint_upper_bounds()[row];
if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
// TODO(user): we currently count a free row as a range constraint.
// Add a new category?
++num_range_constraints;
continue;
}
if (lower_bound == upper_bound) {
++num_equal_constraints;
if (lower_bound != 0) {
++num_rhs_non_zeros;
}
continue;
}
if (lower_bound == -kInfinity) {
++num_less_than_constraints;
if (upper_bound != 0) {
++num_rhs_non_zeros;
}
continue;
}
if (upper_bound == kInfinity) {
++num_greater_than_constraints;
if (lower_bound != 0) {
++num_rhs_non_zeros;
}
continue;
}
LOG(DFATAL) << "There is a bug since all possible cases for the row bounds "
"should have been accounted for. row="
<< row;
}
const int num_integer_variables = IntegerVariablesList().size();
const int num_binary_variables = BinaryVariablesList().size();
const int num_non_binary_variables = NonBinaryVariablesList().size();
const int num_continuous_variables =
ColToIntIndex(num_variables()) - num_integer_variables;
auto format_runtime =
absl::ParsedFormat<'d', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
'd', 'd', 'd', 'd', 'd', 'd', 'd'>::New(format);
CHECK(format_runtime);
return absl::StrFormat(
*format_runtime, RowToIntIndex(num_constraints()),
ColToIntIndex(num_variables()), matrix_.num_entries().value(),
num_objective_non_zeros, num_rhs_non_zeros, num_less_than_constraints,
num_greater_than_constraints, num_equal_constraints,
num_range_constraints, num_non_negative_variables, num_boxed_variables,
num_free_variables, num_fixed_variables, num_other_variables,
num_integer_variables, num_binary_variables, num_non_binary_variables,
num_continuous_variables);
}
std::string LinearProgram::NonZeroStatFormatter(
const absl::string_view format) const {
StrictITIVector<RowIndex, EntryIndex> num_entries_in_row(num_constraints(),
EntryIndex(0));
StrictITIVector<ColIndex, EntryIndex> num_entries_in_column(num_variables(),
EntryIndex(0));
EntryIndex num_entries(0);
const ColIndex num_cols = num_variables();
for (ColIndex col(0); col < num_cols; ++col) {
const SparseColumn& sparse_column = GetSparseColumn(col);
num_entries += sparse_column.num_entries();
num_entries_in_column[col] = sparse_column.num_entries();
for (const SparseColumn::Entry e : sparse_column) {
++num_entries_in_row[e.row()];
}
}
// To avoid division by 0 if there are no columns or no rows, we set
// height and width to be at least one.
const int64_t height = std::max(RowToIntIndex(num_constraints()), 1);
const int64_t width = std::max(ColToIntIndex(num_variables()), 1);
const double fill_rate = 100.0 * static_cast<double>(num_entries.value()) /
static_cast<double>(height * width);
auto format_runtime =
absl::ParsedFormat<'f', 'd', 'f', 'f', 'd', 'f', 'f'>::New(format);
return absl::StrFormat(
*format_runtime, fill_rate, GetMaxElement(num_entries_in_row).value(),
Average(num_entries_in_row), StandardDeviation(num_entries_in_row),
GetMaxElement(num_entries_in_column).value(),
Average(num_entries_in_column), StandardDeviation(num_entries_in_column));
}
void LinearProgram::ResizeRowsIfNeeded(RowIndex row) {
DCHECK_GE(row, 0);
if (row >= num_constraints()) {
transpose_matrix_is_consistent_ = false;
matrix_.SetNumRows(row + 1);
constraint_lower_bounds_.resize(row + 1, Fractional(0.0));
constraint_upper_bounds_.resize(row + 1, Fractional(0.0));
constraint_names_.resize(row + 1, "");
}
}
bool LinearProgram::IsInEquationForm() const {
for (RowIndex constraint(0); constraint < num_constraints(); ++constraint) {
if (constraint_lower_bounds_[constraint] != 0.0 ||
constraint_upper_bounds_[constraint] != 0.0) {
return false;
}
}
const ColIndex num_slack_variables =
num_variables() - GetFirstSlackVariable();
return num_constraints().value() == num_slack_variables.value() &&
IsRightMostSquareMatrixIdentity(matrix_);
}
bool LinearProgram::BoundsOfIntegerVariablesAreInteger(
Fractional tolerance) const {
for (const ColIndex col : IntegerVariablesList()) {
if ((IsFinite(variable_lower_bounds_[col]) &&
!IsIntegerWithinTolerance(variable_lower_bounds_[col], tolerance)) ||
(IsFinite(variable_upper_bounds_[col]) &&
!IsIntegerWithinTolerance(variable_upper_bounds_[col], tolerance))) {
VLOG(1) << "Bounds of variable " << col.value() << " are non-integer ("
<< variable_lower_bounds_[col] << ", "
<< variable_upper_bounds_[col] << ").";
return false;
}
}
return true;
}
bool LinearProgram::BoundsOfIntegerConstraintsAreInteger(
Fractional tolerance) const {
// Using transpose for this is faster (complexity = O(number of non zeros in
// matrix)) than directly iterating through entries (complexity = O(number of
// constraints * number of variables)).
const SparseMatrix& transpose = GetTransposeSparseMatrix();
for (RowIndex row = RowIndex(0); row < num_constraints(); ++row) {
bool integer_constraint = true;
for (const SparseColumn::Entry var : transpose.column(RowToColIndex(row))) {
if (!IsVariableInteger(RowToColIndex(var.row()))) {
integer_constraint = false;
break;
}
// To match what the IntegerBoundsPreprocessor is doing, we require all
// coefficient to be EXACTLY integer here.
if (std::round(var.coefficient()) != var.coefficient()) {
integer_constraint = false;
break;
}
}
if (integer_constraint) {
if ((IsFinite(constraint_lower_bounds_[row]) &&
!IsIntegerWithinTolerance(constraint_lower_bounds_[row],
tolerance)) ||
(IsFinite(constraint_upper_bounds_[row]) &&
!IsIntegerWithinTolerance(constraint_upper_bounds_[row],
tolerance))) {
VLOG(1) << "Bounds of constraint " << row.value()
<< " are non-integer (" << constraint_lower_bounds_[row] << ", "
<< constraint_upper_bounds_[row] << ").";
return false;
}
}
}
return true;
}
void LinearProgram::RemoveNearZeroEntries(Fractional threshold) {
int64_t num_removed_objective_entries = 0;
int64_t num_removed_matrix_entries = 0;
for (ColIndex col(0); col < matrix_.num_cols(); ++col) {
const int64_t old_size = matrix_.column(col).num_entries().value();
matrix_.mutable_column(col)->RemoveNearZeroEntries(threshold);
num_removed_matrix_entries +=
old_size - matrix_.column(col).num_entries().value();
if (std::abs(objective_coefficients_[col]) <= threshold) {
objective_coefficients_[col] = 0.0;
++num_removed_objective_entries;
}
}
if (num_removed_matrix_entries > 0) {
transpose_matrix_is_consistent_ = false;
VLOG(1) << "Removed " << num_removed_matrix_entries << " matrix entries.";
}
if (num_removed_objective_entries > 0) {
VLOG(1) << "Removed " << num_removed_objective_entries
<< " objective coefficients.";
}
}
// --------------------------------------------------------
// ProblemSolution
// --------------------------------------------------------
std::string ProblemSolution::DebugString() const {
std::string s = "Problem status: " + GetProblemStatusString(status);
for (ColIndex col(0); col < primal_values.size(); ++col) {
absl::StrAppendFormat(&s, "\n Var #%d: %s %g", col.value(),
GetVariableStatusString(variable_statuses[col]),
primal_values[col]);
}
s += "\n------------------------------";
for (RowIndex row(0); row < dual_values.size(); ++row) {
absl::StrAppendFormat(&s, "\n Constraint #%d: %s %g", row.value(),
GetConstraintStatusString(constraint_statuses[row]),
dual_values[row]);
}
return s;
}
} // namespace glop
} // namespace operations_research