Remove CoeffMap typedef in linear_solver

This commit is contained in:
Laurent Perron
2018-07-24 09:32:14 -07:00
parent be233ca283
commit a9dd4aec9a
10 changed files with 143 additions and 84 deletions

View File

@@ -331,7 +331,7 @@ void BopInterface::ExtractNewConstraints() {
DCHECK_EQ(new_row, row);
linear_program_.SetConstraintBounds(row, lb, ub);
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
const int var_index = entry.first->index();
DCHECK(variable_is_extracted(var_index));
const glop::ColIndex col(var_index);
@@ -344,7 +344,7 @@ void BopInterface::ExtractNewConstraints() {
// TODO(user): remove duplication with GlopInterface.
void BopInterface::ExtractObjective() {
linear_program_.SetObjectiveOffset(solver_->Objective().offset());
for (CoeffEntry entry : solver_->objective_->coefficients_) {
for (const auto& entry : solver_->objective_->coefficients_) {
const int var_index = entry.first->index();
const glop::ColIndex col(var_index);
const double coeff = entry.second;

View File

@@ -306,7 +306,7 @@ MPSolver::ResultStatus CBCInterface::Solve(const MPSolverParameters& param) {
MPConstraint* const ct = solver_->constraints_[i];
const int size = ct->coefficients_.size();
int j = 0;
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
const int index = MPSolverVarIndexToCbcVarIndex(entry.first->index());
indices[j] = index;
coefs[j] = entry.second;

View File

@@ -217,7 +217,7 @@ void CLPInterface::ClearConstraint(MPConstraint* const constraint) {
InvalidateSolutionSynchronization();
// Constraint may not have been extracted yet.
if (!constraint_is_extracted(constraint->index())) return;
for (CoeffEntry entry : constraint->coefficients_) {
for (const auto& entry : constraint->coefficients_) {
DCHECK(variable_is_extracted(entry.first->index()));
clp_->modifyCoefficient(constraint->index(),
MPSolverVarIndexToClpVarIndex(entry.first->index()),
@@ -249,7 +249,7 @@ void CLPInterface::SetObjectiveOffset(double offset) {
void CLPInterface::ClearObjective() {
InvalidateSolutionSynchronization();
// Clear linear terms
for (CoeffEntry entry : solver_->objective_->coefficients_) {
for (const auto& entry : solver_->objective_->coefficients_) {
const int mpsolver_var_index = entry.first->index();
// Variable may have not been extracted yet.
if (!variable_is_extracted(mpsolver_var_index)) {
@@ -323,7 +323,7 @@ void CLPInterface::ExtractNewVariables() {
for (int i = 0; i < last_constraint_index_; i++) {
MPConstraint* const ct = solver_->constraints_[i];
const int ct_index = ct->index();
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
const int mpsolver_var_index = entry.first->index();
DCHECK(variable_is_extracted(mpsolver_var_index));
if (mpsolver_var_index >= last_variable_index_) {
@@ -368,7 +368,7 @@ void CLPInterface::ExtractNewConstraints() {
size = 1;
}
int j = 0;
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
const int mpsolver_var_index = entry.first->index();
DCHECK(variable_is_extracted(mpsolver_var_index));
indices[j] = MPSolverVarIndexToClpVarIndex(mpsolver_var_index);
@@ -392,7 +392,7 @@ void CLPInterface::ExtractNewConstraints() {
void CLPInterface::ExtractObjective() {
// Linear objective: set objective coefficients for all variables
// (some might have been modified)
for (CoeffEntry entry : solver_->objective_->coefficients_) {
for (const auto& entry : solver_->objective_->coefficients_) {
clp_->setObjectiveCoefficient(
MPSolverVarIndexToClpVarIndex(entry.first->index()), entry.second);
}

View File

@@ -304,7 +304,7 @@ void GLOPInterface::ExtractNewConstraints() {
DCHECK_EQ(new_row, row);
linear_program_.SetConstraintBounds(row, lb, ub);
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
const int var_index = entry.first->index();
DCHECK(variable_is_extracted(var_index));
const glop::ColIndex col(var_index);
@@ -316,7 +316,7 @@ void GLOPInterface::ExtractNewConstraints() {
void GLOPInterface::ExtractObjective() {
linear_program_.SetObjectiveOffset(solver_->Objective().offset());
for (CoeffEntry entry : solver_->objective_->coefficients_) {
for (const auto& entry : solver_->objective_->coefficients_) {
const int var_index = entry.first->index();
const glop::ColIndex col(var_index);
const double coeff = entry.second;

View File

@@ -357,7 +357,7 @@ void GLPKInterface::SetObjectiveOffset(double value) {
// Clear objective of all its terms (linear)
void GLPKInterface::ClearObjective() {
InvalidateSolutionSynchronization();
for (CoeffEntry entry : solver_->objective_->coefficients_) {
for (const auto& entry : solver_->objective_->coefficients_) {
const int mpsolver_var_index = entry.first->index();
// Variable may have not been extracted yet.
if (!variable_is_extracted(mpsolver_var_index)) {
@@ -432,7 +432,7 @@ void GLPKInterface::ExtractOneConstraint(MPConstraint* const constraint,
double* const coefs) {
// GLPK convention is to start indexing at 1.
int k = 1;
for (CoeffEntry entry : constraint->coefficients_) {
for (const auto& entry : constraint->coefficients_) {
DCHECK(variable_is_extracted(entry.first->index()));
indices[k] = MPSolverIndexToGlpkIndex(entry.first->index());
coefs[k] = entry.second;
@@ -477,7 +477,7 @@ void GLPKInterface::ExtractNewConstraints() {
int k = 1;
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* ct = solver_->constraints_[i];
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
DCHECK(variable_is_extracted(entry.first->index()));
constraint_indices[k] = MPSolverIndexToGlpkIndex(ct->index());
variable_indices[k] = MPSolverIndexToGlpkIndex(entry.first->index());
@@ -507,7 +507,7 @@ void GLPKInterface::ExtractNewConstraints() {
void GLPKInterface::ExtractObjective() {
// Linear objective: set objective coefficients for all variables
// (some might have been modified).
for (CoeffEntry entry : solver_->objective_->coefficients_) {
for (const auto& entry : solver_->objective_->coefficients_) {
glp_set_obj_coef(lp_, MPSolverIndexToGlpkIndex(entry.first->index()),
entry.second);
}

View File

@@ -143,6 +143,9 @@ class GurobiInterface : public MPSolverInterface {
// }
}
// Iterates through the solutions in Gurobi's solution pool.
bool NextSolution() override;
private:
// Sets all parameters in the underlying solver.
void SetParameters(const MPSolverParameters& param) override;
@@ -167,16 +170,21 @@ class GurobiInterface : public MPSolverInterface {
<< GRBgeterrormsg(env_);
}
private:
int SolutionCount() const;
GRBmodel* model_;
GRBenv* env_;
bool mip_;
int current_solution_index_;
};
// Creates a LP/MIP instance with the specified name and minimization objective.
GurobiInterface::GurobiInterface(MPSolver* const solver, bool mip)
: MPSolverInterface(solver), model_(nullptr), env_(nullptr), mip_(mip) {
: MPSolverInterface(solver),
model_(nullptr),
env_(nullptr),
mip_(mip),
current_solution_index_(0) {
int ret = GRBloadenv(&env_, nullptr);
if (ret != 0 || env_ == nullptr) {
std::string err_msg = GRBgeterrormsg(env_);
@@ -497,7 +505,7 @@ void GurobiInterface::ExtractNewConstraints() {
CHECK(constraint_is_extracted(row));
const int size = ct->coefficients_.size();
int col = 0;
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
const int var_index = entry.first->index();
CHECK(variable_is_extracted(var_index));
col_indices[col] = var_index;
@@ -621,6 +629,13 @@ void GurobiInterface::SetLpAlgorithm(int value) {
}
}
int GurobiInterface::SolutionCount() const {
int solution_count = 0;
CheckedGurobiCall(
GRBgetintattr(model_, GRB_INT_ATTR_SOLCOUNT, &solution_count));
return solution_count;
}
MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
WallTimer timer;
timer.Start();
@@ -681,9 +696,7 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
CheckedGurobiCall(
GRBgetintattr(model_, GRB_INT_ATTR_STATUS, &optimization_status));
VLOG(1) << StringPrintf("Solution status %d.\n", optimization_status);
int solution_count = 0;
CheckedGurobiCall(
GRBgetintattr(model_, GRB_INT_ATTR_SOLCOUNT, &solution_count));
const int solution_count = SolutionCount();
switch (optimization_status) {
case GRB_OPTIMAL:
@@ -714,41 +727,47 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
if (solution_count > 0 && (result_status_ == MPSolver::FEASIBLE ||
result_status_ == MPSolver::OPTIMAL)) {
current_solution_index_ = 0;
// Get the results.
const int total_num_rows = solver_->constraints_.size();
const int total_num_cols = solver_->variables_.size();
std::unique_ptr<double[]> values(new double[total_num_cols]);
std::unique_ptr<double[]> dual_values(new double[total_num_rows]);
std::unique_ptr<double[]> reduced_costs(new double[total_num_cols]);
CheckedGurobiCall(
GRBgetdblattr(model_, GRB_DBL_ATTR_OBJVAL, &objective_value_));
CheckedGurobiCall(GRBgetdblattrarray(model_, GRB_DBL_ATTR_X, 0,
total_num_cols, values.get()));
if (!mip_) {
{
std::vector<double> variable_values(total_num_cols);
CheckedGurobiCall(
GRBgetdblattr(model_, GRB_DBL_ATTR_OBJVAL, &objective_value_));
CheckedGurobiCall(GRBgetdblattrarray(
model_, GRB_DBL_ATTR_RC, 0, total_num_cols, reduced_costs.get()));
CheckedGurobiCall(GRBgetdblattrarray(model_, GRB_DBL_ATTR_PI, 0,
total_num_rows, dual_values.get()));
}
model_, GRB_DBL_ATTR_X, 0, total_num_cols, variable_values.data()));
VLOG(1) << "objective = " << objective_value_;
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
var->set_solution_value(values[i]);
VLOG(3) << var->name() << ", value = " << values[i];
if (!mip_) {
var->set_reduced_cost(reduced_costs[i]);
VLOG(4) << var->name() << ", reduced cost = " << reduced_costs[i];
VLOG(1) << "objective = " << objective_value_;
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
var->set_solution_value(variable_values[i]);
VLOG(3) << var->name() << ", value = " << variable_values[i];
}
}
if (!mip_) {
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* const ct = solver_->constraints_[i];
ct->set_dual_value(dual_values[i]);
VLOG(4) << "row " << ct->index() << ", dual value = " << dual_values[i];
{
std::vector<double> reduced_costs(total_num_cols);
CheckedGurobiCall(GRBgetdblattrarray(
model_, GRB_DBL_ATTR_RC, 0, total_num_cols, reduced_costs.data()));
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
var->set_reduced_cost(reduced_costs[i]);
VLOG(4) << var->name() << ", reduced cost = " << reduced_costs[i];
}
}
{
std::vector<double> dual_values(total_num_rows);
CheckedGurobiCall(GRBgetdblattrarray(
model_, GRB_DBL_ATTR_PI, 0, total_num_rows, dual_values.data()));
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* const ct = solver_->constraints_[i];
ct->set_dual_value(dual_values[i]);
VLOG(4) << "row " << ct->index()
<< ", dual value = " << dual_values[i];
}
}
}
}
@@ -758,6 +777,39 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
return result_status_;
}
bool GurobiInterface::NextSolution() {
// Next solution only supported for MIP
if (!mip_) return false;
// Make sure we have successfully solved the problem and not modified it.
if (!CheckSolutionIsSynchronizedAndExists()) {
return false;
}
// Check if we are out of solutions.
if (current_solution_index_ + 1 >= SolutionCount()) {
return false;
}
current_solution_index_++;
const int total_num_cols = solver_->variables_.size();
std::vector<double> variable_values(total_num_cols);
CheckedGurobiCall(GRBsetintparam(
GRBgetenv(model_), GRB_INT_PAR_SOLUTIONNUMBER, current_solution_index_));
CheckedGurobiCall(
GRBgetdblattr(model_, GRB_DBL_ATTR_POOLOBJVAL, &objective_value_));
CheckedGurobiCall(GRBgetdblattrarray(model_, GRB_DBL_ATTR_XN, 0,
total_num_cols, variable_values.data()));
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
var->set_solution_value(variable_values[i]);
}
// TODO(user,user): This reset may not be necessary, investigate.
GRBresetparams(GRBgetenv(model_));
return true;
}
void GurobiInterface::Write(const std::string& filename) {
if (sync_status_ == MUST_RELOAD) {
Reset();

View File

@@ -75,7 +75,7 @@ void MPConstraint::SetCoefficient(const MPVariable* const var, double coeff) {
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
if (var == nullptr) return;
if (coeff == 0.0) {
CoeffMap::iterator it = coefficients_.find(var);
auto it = coefficients_.find(var);
// If setting a coefficient to 0 when this coefficient did not
// exist or was already 0, do nothing: skip
// interface_->SetCoefficient() and do not store a coefficient in
@@ -90,8 +90,7 @@ void MPConstraint::SetCoefficient(const MPVariable* const var, double coeff) {
}
return;
}
std::pair<CoeffMap::iterator, bool> insertion_result =
coefficients_.insert(std::make_pair(var, coeff));
auto insertion_result = coefficients_.insert(std::make_pair(var, coeff));
const double old_value =
insertion_result.second ? 0.0 : insertion_result.first->second;
insertion_result.first->second = coeff;
@@ -135,7 +134,7 @@ MPSolver::BasisStatus MPConstraint::basis_status() const {
bool MPConstraint::ContainsNewVariables() {
const int last_variable_index = interface_->last_variable_index();
for (CoeffEntry entry : coefficients_) {
for (const auto& entry : coefficients_) {
const int variable_index = entry.first->index();
if (variable_index >= last_variable_index ||
!interface_->variable_is_extracted(variable_index)) {
@@ -157,7 +156,7 @@ void MPObjective::SetCoefficient(const MPVariable* const var, double coeff) {
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
if (var == nullptr) return;
if (coeff == 0.0) {
CoeffMap::iterator it = coefficients_.find(var);
auto it = coefficients_.find(var);
// See the discussion on MPConstraint::SetCoefficient() for 0 coefficients,
// the same reasoning applies here.
if (it == coefficients_.end() || it->second == 0.0) return;
@@ -541,9 +540,6 @@ MPSolverResponseStatus MPSolver::LoadModelFromProto(
// duplicate names in the proto (they're not considered as 'ids'),
// unlike the MPSolver C++ API which crashes if there are duplicate names.
// Clearing the names makes the MPSolver generate unique names.
//
// TODO(user): This limits the number of variables and constraints to 10^9:
// we should fix that.
return LoadModelFromProtoInternal(input_model, /*clear_names=*/true,
error_message);
}
@@ -659,6 +655,10 @@ void MPSolver::FillSolutionResponseProto(MPSolutionResponse* response) const {
for (int j = 0; j < constraints_.size(); ++j) {
response->add_dual_value(constraints_[j]->dual_value());
}
// Reduced cost have no meaning in MIP.
for (int i = 0; i < variables_.size(); ++i) {
response->add_reduced_cost(variables_[i]->reduced_cost());
}
}
}
}
@@ -738,7 +738,7 @@ void MPSolver::ExportModelToProto(MPModelProto* output_model) const {
// Vector linear_term will contain pairs (variable index, coeff), that will
// be sorted by variable index.
std::vector<std::pair<int, double> > linear_term;
for (CoeffEntry entry : constraint->coefficients_) {
for (const auto& entry : constraint->coefficients_) {
const MPVariable* const var = entry.first;
const int var_index = gtl::FindWithDefault(var_to_index, var, -1);
DCHECK_NE(-1, var_index);
@@ -1088,7 +1088,7 @@ std::vector<double> MPSolver::ComputeConstraintActivities() const {
for (int i = 0; i < constraints_.size(); ++i) {
const MPConstraint& constraint = *constraints_[i];
AccurateSum<double> sum;
for (CoeffEntry entry : constraint.coefficients_) {
for (const auto& entry : constraint.coefficients_) {
sum.Add(entry.first->solution_value() * entry.second);
}
activities[i] = sum.Value();
@@ -1156,7 +1156,7 @@ bool MPSolver::VerifySolution(double tolerance, bool log_errors) const {
const double activity = activities[i];
// Re-compute the activity with a inaccurate summing algorithm.
double inaccurate_activity = 0.0;
for (CoeffEntry entry : constraint.coefficients_) {
for (const auto& entry : constraint.coefficients_) {
inaccurate_activity += entry.first->solution_value() * entry.second;
}
// Catch NaNs.
@@ -1203,7 +1203,7 @@ bool MPSolver::VerifySolution(double tolerance, bool log_errors) const {
AccurateSum<double> objective_sum;
objective_sum.Add(objective.offset());
double inaccurate_objective_value = objective.offset();
for (CoeffEntry entry : objective.coefficients_) {
for (const auto& entry : objective.coefficients_) {
const double term = entry.first->solution_value() * entry.second;
objective_sum.Add(term);
inaccurate_objective_value += term;

View File

@@ -325,9 +325,6 @@ class MPSolver {
// homonymous enum values of MPSolutionResponse::Status
// (see ./linear_solver.proto) is guaranteed by ./enum_consistency_test.cc,
// you may rely on it.
// TODO(user): Figure out once and for all what the status of
// underlying solvers exactly mean, especially for feasible and
// infeasible.
enum ResultStatus {
OPTIMAL, // optimal.
FEASIBLE, // feasible, or stopped by limit.
@@ -572,6 +569,20 @@ class MPSolver {
// is ill conditioned.
double ComputeExactConditionNumber() const;
// Some solvers (MIP only, not LP) can produce multiple solutions to the
// problem. Returns true when another solution is available, and updates the
// MPVariable* objects to make the new solution queryable. Call only after
// calling solve.
//
// The optimality properties of the additional solutions found, and whether
// or not the solver computes them ahead of time or when NextSolution() is
// called is solver specific.
//
// As of July 17, 2018, only Gurobi supports NextSolution(), see
// linear_solver_underlying_gurobi_test for an example of how to configure
// Gurobi for this purpose. The other solvers return false unconditionally.
MUST_USE_RESULT bool NextSolution();
friend class GLPKInterface;
friend class CLPInterface;
friend class CBCInterface;
@@ -655,21 +666,6 @@ inline std::ostream& operator<<(std::ostream& os,
static_cast<MPSolverResponseStatus>(status));
}
// The data structure used to store the coefficients of the contraints and of
// the objective. Also define a type to facilitate iteration over them with:
// for (CoeffEntry entry : coefficients_) { ... }
class CoeffMap : public std::unordered_map<const MPVariable*, double> {
public:
explicit CoeffMap(int num_buckets)
#if !defined(_MSC_VER) // Visual C++ doesn't support this constructor
: std::unordered_map<const MPVariable*, double>(num_buckets)
#endif // _MSC_VER
{
}
};
typedef std::pair<const MPVariable*, double> CoeffEntry;
// A class to express a linear objective.
class MPObjective {
public:
@@ -760,7 +756,7 @@ class MPObjective {
MPSolverInterface* const interface_;
// Mapping var -> coefficient.
CoeffMap coefficients_;
std::unordered_map<const MPVariable*, double> coefficients_;
// Constant term.
double offset_;
@@ -948,7 +944,7 @@ class MPConstraint {
bool ContainsNewVariables();
// Mapping var -> coefficient.
CoeffMap coefficients_;
std::unordered_map<const MPVariable*, double> coefficients_;
const int index_; // See index().
@@ -1303,6 +1299,9 @@ class MPSolverInterface {
virtual bool InterruptSolve() { return false; }
// See MPSolver::NextSolution() for contract.
virtual bool NextSolution() { return false; }
friend class MPSolver;
// To access the maximize_ bool and the MPSolver.

View File

@@ -376,4 +376,12 @@ message MPSolutionResponse {
// it is actually a linear program).
// These are set iff 'status' is OPTIMAL or FEASIBLE.
repeated double dual_value = 4 [packed = true];
// [Advanced usage.]
// Values of the reduced cost of the variables in the same order as the
// MPModelProto::variable. This is a dense representation.
// These are not set if the problem was solved with a MIP solver (even if it
// is actually a linear program).
// These are set iff 'status' is OPTIMAL or FEASIBLE.
repeated double reduced_cost = 6 [packed = true];
}

View File

@@ -264,7 +264,7 @@ void SCIPInterface::ClearConstraint(MPConstraint* constraint) {
const int constraint_index = constraint->index();
// Constraint may not have been extracted yet.
if (!constraint_is_extracted(constraint_index)) return;
for (CoeffEntry entry : constraint->coefficients_) {
for (const auto& entry : constraint->coefficients_) {
const int var_index = entry.first->index();
const double old_coef_value = entry.second;
DCHECK(variable_is_extracted(var_index));
@@ -292,7 +292,7 @@ void SCIPInterface::ClearObjective() {
InvalidateSolutionSynchronization();
ORTOOLS_SCIP_CALL(SCIPfreeTransform(scip_));
// Clear linear terms
for (CoeffEntry entry : solver_->objective_->coefficients_) {
for (const auto& entry : solver_->objective_->coefficients_) {
const int var_index = entry.first->index();
// Variable may have not been extracted yet.
if (!variable_is_extracted(var_index)) {
@@ -334,7 +334,7 @@ void SCIPInterface::ExtractNewVariables() {
// Add new variables to existing constraints.
for (int i = 0; i < last_constraint_index_; i++) {
MPConstraint* const ct = solver_->constraints_[i];
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
const int var_index = entry.first->index();
DCHECK(variable_is_extracted(var_index));
if (var_index >= last_variable_index_) {
@@ -371,7 +371,7 @@ void SCIPInterface::ExtractNewConstraints() {
DCHECK(constraint_is_extracted(i));
const int size = ct->coefficients_.size();
int j = 0;
for (CoeffEntry entry : ct->coefficients_) {
for (const auto& entry : ct->coefficients_) {
const int var_index = entry.first->index();
DCHECK(variable_is_extracted(var_index));
vars[j] = scip_variables_[var_index];
@@ -406,7 +406,7 @@ void SCIPInterface::ExtractObjective() {
ORTOOLS_SCIP_CALL(SCIPfreeTransform(scip_));
// Linear objective: set objective coefficients for all variables (some might
// have been modified).
for (CoeffEntry entry : solver_->objective_->coefficients_) {
for (const auto& entry : solver_->objective_->coefficients_) {
const int var_index = entry.first->index();
const double obj_coef = entry.second;
ORTOOLS_SCIP_CALL(