huge sync with internal version

This commit is contained in:
lperron@google.com
2013-06-11 14:49:45 +00:00
parent 874aa36ead
commit 5e40dfea6b
11 changed files with 485 additions and 378 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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
@@ -63,9 +63,6 @@ class CBCInterface : public MPSolverInterface {
// TODO(user): separate the solve from the model extraction.
virtual void ExtractModel() {}
// Write model
virtual void WriteModel(const string& filename);
// Query problem type.
virtual bool IsContinuous() const { return false; }
virtual bool IsLP() const { return false; }
@@ -202,16 +199,6 @@ void CBCInterface::SetOptimizationDirection(bool maximize) {
}
}
void CBCInterface::WriteModel(const string& filename) {
if (HasSuffixString(filename, ".lp")) {
osi_.writeLp(filename.c_str(), "");
} else {
// If filename does not end in ".gz", CBC will
// append ".gz" to the filename.
osi_.writeMps(filename.c_str(), "");
}
}
void CBCInterface::SetVariableBounds(int var_index, double lb, double ub) {
InvalidateSolutionSynchronization();
if (sync_status_ == MODEL_SYNCHRONIZED) {
@@ -349,8 +336,6 @@ MPSolver::ResultStatus CBCInterface::Solve(const MPSolverParameters& param) {
sync_status_ = MODEL_SYNCHRONIZED;
VLOG(1) << StringPrintf("Model built in %.3f seconds.", timer.Get());
WriteModelToPredefinedFiles();
ResetBestObjectiveBound();
// Solve
@@ -397,35 +382,6 @@ MPSolver::ResultStatus CBCInterface::Solve(const MPSolverParameters& param) {
VLOG(1) << StringPrintf("Solved in %.3f seconds.", timer.Get());
// Get the results
objective_value_ = model.getObjValue();
VLOG(1) << "objective=" << objective_value_;
const double* const values = model.bestSolution();
if (values != NULL) {
// if optimal or feasible solution is found.
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
const int var_index = var->index();
const double val = values[var_index];
var->set_solution_value(val);
VLOG(3) << var->name() << "=" << val;
}
} else {
VLOG(1) << "No feasible solution found.";
}
const double* const row_activities = model.getRowActivity();
if (row_activities != NULL) {
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* const ct = solver_->constraints_[i];
const int constraint_index = ct->index();
const double row_activity = row_activities[constraint_index];
ct->set_activity(row_activity);
VLOG(4) << "row " << ct->index()
<< ": activity = " << row_activity;
}
}
// Check the status: optimal, infeasible, etc.
int tmp_status = model.status();
@@ -466,6 +422,38 @@ MPSolver::ResultStatus CBCInterface::Solve(const MPSolverParameters& param) {
break;
}
if (result_status_ == MPSolver::OPTIMAL ||
result_status_ == MPSolver::FEASIBLE) {
// Get the results
objective_value_ = model.getObjValue();
VLOG(1) << "objective=" << objective_value_;
const double* const values = model.bestSolution();
if (values != NULL) {
// if optimal or feasible solution is found.
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
const int var_index = var->index();
const double val = values[var_index];
var->set_solution_value(val);
VLOG(3) << var->name() << "=" << val;
}
} else {
VLOG(1) << "No feasible solution found.";
}
const double* const row_activities = model.getRowActivity();
if (row_activities != NULL) {
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* const ct = solver_->constraints_[i];
const int constraint_index = ct->index();
const double row_activity = row_activities[constraint_index];
ct->set_activity(row_activity);
VLOG(4) << "row " << ct->index()
<< ": activity = " << row_activity;
}
}
}
iterations_ = model.getIterationCount();
nodes_ = model.getNodeCount();
best_objective_bound_ = model.getBestPossibleObjValue();

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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
@@ -98,9 +98,6 @@ class CLPInterface : public MPSolverInterface {
virtual MPSolver::BasisStatus column_status(int variable_index) const;
// ----- Misc -----
// Write model
virtual void WriteModel(const string& filename);
// Query problem type.
virtual bool IsContinuous() const { return true; }
virtual bool IsLP() const { return true; }
@@ -162,17 +159,6 @@ void CLPInterface::Reset() {
ResetExtractionInformation();
}
void CLPInterface::WriteModel(const string& filename) {
// CLP does not support the LP format natively. It only supports it
// through OsiClpSolverInterface.
// TODO(user) : Implement support for .lp format.
if (HasSuffixString(filename, ".lp")) {
LOG(WARNING) << "CLP does not support the LP format, "
<< "writing in MPS format instead.";
}
clp_->writeMps(filename.c_str());
}
// ------ Model modifications and extraction -----
// Not cached
@@ -244,12 +230,20 @@ void CLPInterface::ClearConstraint(MPConstraint* const constraint) {
// Cached
void CLPInterface::SetObjectiveCoefficient(const MPVariable* const variable,
double coefficient) {
sync_status_ = MUST_RELOAD;
InvalidateSolutionSynchronization();
if (variable->index() != kNoIndex) {
clp_->setObjectiveCoefficient(variable->index(), coefficient);
} else {
sync_status_ = MUST_RELOAD;
}
}
// Cached
void CLPInterface::SetObjectiveOffset(double value) {
sync_status_ = MUST_RELOAD;
void CLPInterface::SetObjectiveOffset(double offset) {
// Constant term. Use -offset instead of +offset because CLP does
// not follow conventions.
InvalidateSolutionSynchronization();
clp_->setObjectiveOffset(-offset);
}
// Clear objective of all its terms.
@@ -329,13 +323,14 @@ void CLPInterface::ExtractNewVariables() {
// Add new variables to existing constraints.
for (int i = 0; i < last_constraint_index_; i++) {
MPConstraint* const ct = solver_->constraints_[i];
const int ct_index = ct->index();
for (ConstIter<hash_map<const MPVariable*, double> > it(
ct->coefficients_);
!it.at_end(); ++it) {
const int var_index = it->first->index();
DCHECK_NE(kNoIndex, var_index);
if (var_index >= last_variable_index_) {
clp_->modifyCoefficient(i, var_index, it->second);
if (var_index >= last_variable_index_ + 1) { // + 1 because of dummy.
clp_->modifyCoefficient(ct_index, var_index, it->second);
}
}
}
@@ -448,8 +443,6 @@ MPSolver::ResultStatus CLPInterface::Solve(const MPSolverParameters& param) {
ExtractModel();
VLOG(1) << StringPrintf("Model built in %.3f seconds.", timer.Get());
WriteModelToPredefinedFiles();
// Time limit.
if (solver_->time_limit()) {
VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
@@ -468,35 +461,6 @@ MPSolver::ResultStatus CLPInterface::Solve(const MPSolverParameters& param) {
clp_->initialSolve(*options_);
VLOG(1) << StringPrintf("Solved in %.3f seconds.", timer.Get());
// Get the results
objective_value_ = clp_->objectiveValue();
VLOG(1) << "objective=" << objective_value_;
const double* const values = clp_->getColSolution();
const double* const reduced_costs = clp_->getReducedCost();
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
const int var_index = var->index();
double val = values[var_index];
var->set_solution_value(val);
VLOG(3) << var->name() << ": value = " << val;
double reduced_cost = reduced_costs[var_index];
var->set_reduced_cost(reduced_cost);
VLOG(4) << var->name() << ": reduced cost = " << reduced_cost;
}
const double* const dual_values = clp_->getRowPrice();
const double* const row_activities = clp_->getRowActivity();
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* const ct = solver_->constraints_[i];
const int constraint_index = ct->index();
const double row_activity = row_activities[constraint_index];
ct->set_activity(row_activity);
const double dual_value = dual_values[constraint_index];
ct->set_dual_value(dual_value);
VLOG(4) << "row " << ct->index()
<< ": activity = " << row_activity
<< " dual value = " << dual_value;
}
// Check the status: optimal, infeasible, etc.
int tmp_status = clp_->status();
VLOG(1) << "clp result status: " << tmp_status;
@@ -518,6 +482,38 @@ MPSolver::ResultStatus CLPInterface::Solve(const MPSolverParameters& param) {
break;
}
if (result_status_ == MPSolver::OPTIMAL ||
result_status_ == MPSolver::FEASIBLE) {
// Get the results
objective_value_ = clp_->objectiveValue();
VLOG(1) << "objective=" << objective_value_;
const double* const values = clp_->getColSolution();
const double* const reduced_costs = clp_->getReducedCost();
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
const int var_index = var->index();
double val = values[var_index];
var->set_solution_value(val);
VLOG(3) << var->name() << ": value = " << val;
double reduced_cost = reduced_costs[var_index];
var->set_reduced_cost(reduced_cost);
VLOG(4) << var->name() << ": reduced cost = " << reduced_cost;
}
const double* const dual_values = clp_->getRowPrice();
const double* const row_activities = clp_->getRowActivity();
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* const ct = solver_->constraints_[i];
const int constraint_index = ct->index();
const double row_activity = row_activities[constraint_index];
ct->set_activity(row_activity);
const double dual_value = dual_values[constraint_index];
ct->set_dual_value(dual_value);
VLOG(4) << "row " << ct->index()
<< ": activity = " << row_activity
<< " dual value = " << dual_value;
}
}
ResetParameters();
sync_status_ = SOLUTION_SYNCHRONIZED;
return result_status_;

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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
@@ -152,9 +152,6 @@ class GLPKInterface : public MPSolverInterface {
virtual bool CheckBestObjectiveBoundExists() const;
// ----- Misc -----
// Write model
virtual void WriteModel(const string& filename);
// Query problem type.
virtual bool IsContinuous() const { return IsLP(); }
virtual bool IsLP() const { return !mip_; }
@@ -221,8 +218,7 @@ class GLPKInterface : public MPSolverInterface {
// Creates a LP/MIP instance with the specified name and minimization objective.
GLPKInterface::GLPKInterface(MPSolver* const solver, bool mip)
: MPSolverInterface(solver), lp_(NULL), mip_(mip),
mip_callback_info_(NULL) {
: MPSolverInterface(solver), lp_(NULL), mip_(mip) {
lp_ = glp_create_prob();
glp_set_prob_name(lp_, solver_->name_.c_str());
glp_set_obj_dir(lp_, GLP_MIN);
@@ -245,14 +241,6 @@ void GLPKInterface::Reset() {
ResetExtractionInformation();
}
void GLPKInterface::WriteModel(const string& filename) {
if (HasSuffixString(filename, ".lp")) {
glp_write_lp(lp_, NULL, filename.c_str());
} else {
glp_write_mps(lp_, GLP_MPS_FILE, NULL, filename.c_str());
}
}
// ------ Model modifications and extraction -----
// Not cached
@@ -560,8 +548,6 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
ExtractModel();
VLOG(1) << StringPrintf("Model built in %.3f seconds.", timer.Get());
WriteModelToPredefinedFiles();
// Configure parameters at every solve, even when the model has not
// been changed, in case some of the parameters such as the time
// limit have been changed since the last solve.
@@ -704,8 +690,11 @@ int64 GLPKInterface::iterations() const {
return kUnknownNumberOfIterations;
} else {
if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations;
// return lpx_get_int_parm(lp_, LPX_K_ITCNT); // FIXME
#if GLP_MINOR_VERSION >= 49
return kUnknownNumberOfIterations;
#else
return lpx_get_int_parm(lp_, LPX_K_ITCNT);
#endif
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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
@@ -93,9 +93,6 @@ class GurobiInterface : public MPSolverInterface {
virtual MPSolver::BasisStatus column_status(int variable_index) const;
// ----- Misc -----
// Writes model
virtual void WriteModel(const string& filename);
// Queries problem type.
virtual bool IsContinuous() const { return IsLP(); }
virtual bool IsLP() const { return !mip_; }
@@ -154,6 +151,9 @@ class GurobiInterface : public MPSolverInterface {
virtual void SetScalingMode(int value);
virtual void SetLpAlgorithm(int value);
virtual bool ReadParameterFile(const string& filename);
virtual string ValidFileExtensionForParameterFile() const;
MPSolver::BasisStatus
TransformGRBVarBasisStatus(int gurobi_basis_status) const;
MPSolver::BasisStatus
@@ -195,10 +195,6 @@ GurobiInterface::~GurobiInterface() {
GRBfreeenv(env_);
}
void GurobiInterface::WriteModel(const string& filename) {
CHECKED_GUROBI_CALL(GRBwrite(model_, filename.c_str()));
}
// ------ Model modifications and extraction -----
void GurobiInterface::Reset() {
@@ -672,8 +668,6 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
VLOG(1) << StringPrintf("Model build in %.3f seconds.", timer.Get());
WriteModelToPredefinedFiles();
// Time limit.
if (solver_->time_limit()) {
VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
@@ -682,7 +676,8 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
solver_->time_limit() / 1000.0));
}
solver_->SetSolverSpecificParametersAsString(
solver_->solver_specific_parameter_string_);
SetParameters(param);
// Solve
@@ -695,20 +690,57 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
VLOG(1) << StringPrintf("Solved in %.3f seconds.", timer.Get());
}
// Get the results.
int total_num_rows = solver_->constraints_.size();
int total_num_cols = solver_->variables_.size();
scoped_array<double> values(new double[total_num_cols]);
scoped_array<double> dual_values(new double[total_num_rows]);
scoped_array<double> slacks(new double[total_num_rows]);
scoped_array<double> rhs(new double[total_num_rows]);
scoped_array<double> reduced_costs(new double[total_num_cols]);
// Get the status.
int optimization_status = 0;
CHECKED_GUROBI_CALL(GRBgetintattr(model_,
GRB_INT_ATTR_STATUS,
&optimization_status));
if (optimization_status == GRB_OPTIMAL ||
optimization_status == GRB_SUBOPTIMAL) {
VLOG(1) << StringPrintf("Solution status %d.\n", optimization_status);
int solution_count = 0;
CHECKED_GUROBI_CALL(GRBgetintattr(model_,
GRB_INT_ATTR_SOLCOUNT,
&solution_count));
switch (optimization_status) {
case GRB_OPTIMAL:
result_status_ = MPSolver::OPTIMAL;
break;
case GRB_INFEASIBLE:
result_status_ = MPSolver::INFEASIBLE;
break;
case GRB_UNBOUNDED:
result_status_ = MPSolver::UNBOUNDED;
break;
case GRB_INF_OR_UNBD:
// TODO(user,user): We could introduce our own "infeasible or
// unbounded" status.
result_status_ = MPSolver::INFEASIBLE;
break;
default: {
if (solution_count > 0) {
result_status_ = MPSolver::FEASIBLE;
} else {
// TODO(user,user): We could introduce additional values for the
// status: for example, stopped because of time limit.
result_status_ = MPSolver::ABNORMAL;
}
break;
}
}
if (solution_count > 0) {
DCHECK(result_status_ == MPSolver::FEASIBLE ||
result_status_ == MPSolver::OPTIMAL);
// Get the results.
const int total_num_rows = solver_->constraints_.size();
const int total_num_cols = solver_->variables_.size();
scoped_array<double> values(new double[total_num_cols]);
scoped_array<double> dual_values(new double[total_num_rows]);
scoped_array<double> slacks(new double[total_num_rows]);
scoped_array<double> rhs(new double[total_num_rows]);
scoped_array<double> reduced_costs(new double[total_num_cols]);
CHECKED_GUROBI_CALL(GRBgetdblattr(model_,
GRB_DBL_ATTR_OBJVAL,
&objective_value_));
@@ -739,13 +771,7 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
total_num_rows,
dual_values.get()));
}
}
int solution_count = 0;
CHECKED_GUROBI_CALL(GRBgetintattr(model_,
GRB_INT_ATTR_SOLCOUNT,
&solution_count));
if (solution_count > 0) {
VLOG(1) << "objective = " << objective_value_;
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
@@ -756,6 +782,7 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
VLOG(4) << var->name() << ", reduced cost = " << reduced_costs[i];
}
}
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* const ct = solver_->constraints_[i];
ct->set_activity(rhs[i] - slacks[i]);
@@ -773,44 +800,26 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
}
}
VLOG(1) << StringPrintf("Solution status %d.\n", optimization_status);
switch (optimization_status) {
case GRB_OPTIMAL:
result_status_ = MPSolver::OPTIMAL;
break;
case GRB_INFEASIBLE:
result_status_ = MPSolver::INFEASIBLE;
break;
case GRB_UNBOUNDED:
result_status_ = MPSolver::UNBOUNDED;
break;
case GRB_INF_OR_UNBD:
// TODO(user,user): We could introduce our own "infeasible or
// unbounded" status.
result_status_ = MPSolver::INFEASIBLE;
break;
default: {
if (solution_count > 0) {
result_status_ = MPSolver::FEASIBLE;
} else {
// TODO(user,user): We could introduce additional values for the
// status: for example, stopped because of time limit.
result_status_ = MPSolver::ABNORMAL;
}
break;
}
}
sync_status_ = SOLUTION_SYNCHRONIZED;
GRBresetparams(GRBgetenv(model_));
return result_status_;
}
bool GurobiInterface::ReadParameterFile(const string& filename) {
// A non-zero return value indicates that a problem occurred.
return GRBreadparams(GRBgetenv(model_), filename.c_str()) == 0;
}
string GurobiInterface::ValidFileExtensionForParameterFile() const {
return ".prm";
}
MPSolverInterface* BuildGurobiInterface(bool mip, MPSolver* const solver) {
return new GurobiInterface(solver, mip);
}
#undef CHECKED_GUROBI_CALL
} // namespace operations_research
#endif // #if defined(USE_GUROBI)

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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
@@ -11,7 +11,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
// (Laurent Perron)
#include "linear_solver/linear_solver.h"
@@ -33,16 +32,15 @@ using std::isnan;
#include "base/scoped_ptr.h"
#include "base/stringprintf.h"
#include "base/timer.h"
#include "base/file.h"
#include "base/concise_iterator.h"
#include "base/map-util.h"
#include "base/stl_util.h"
#include "base/hash.h"
#include "linear_solver/linear_solver.pb.h"
#include "linear_solver/model_exporter.h"
#include "util/accurate_sum.h"
DEFINE_string(solver_write_model,
"",
"Path of the file to write the model to.");
#include "util/fp_utils.h"
DEFINE_bool(verify_solution, false,
"Systematically verify the solution when calling Solve()"
@@ -51,6 +49,10 @@ DEFINE_bool(verify_solution, false,
DEFINE_bool(log_verification_errors, true,
"If --verify_solution is set: LOG(ERROR) all errors detected"
" during the verification of the solution.");
DEFINE_bool(linear_solver_enable_verbose_output, false,
"If set, enables verbose output for the solver. Setting this flag"
" is the same as calling MPSolver::EnableOutput().");
// To compile the open-source code, the anonymous namespace should be
// inside the operations_research namespace (This is due to the
@@ -68,7 +70,7 @@ void MPConstraint::SetCoefficient(const MPVariable* const var, double coeff) {
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
if (var == NULL) return;
if (coeff == 0.0) {
hash_map<const MPVariable*, double>::iterator it = coefficients_.find(var);
CoeffMap::iterator 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
@@ -83,8 +85,7 @@ void MPConstraint::SetCoefficient(const MPVariable* const var, double coeff) {
}
return;
}
std::pair<hash_map<const MPVariable*, double>::iterator, bool>
insertion_result =
std::pair<CoeffMap::iterator, bool> insertion_result =
coefficients_.insert(std::make_pair(var, coeff));
const double old_value =
insertion_result.second ? 0.0 : insertion_result.first->second;
@@ -157,7 +158,7 @@ void MPObjective::SetCoefficient(const MPVariable* const var, double coeff) {
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var;
if (var == NULL) return;
if (coeff == 0.0) {
hash_map<const MPVariable*, double>::iterator it = coefficients_.find(var);
CoeffMap::iterator 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;
@@ -309,6 +310,46 @@ void* MPSolver::underlying_solver() {
return interface_->underlying_solver();
}
// ---- Solver-specific parameters ----
bool MPSolver::SetSolverSpecificParametersAsString(const string& parameters) {
solver_specific_parameter_string_ = parameters;
// Note(user): this method needs to return a success/failure boolean
// immediately, so we also perform the actual parameter parsing right away.
// Some implementations will keep them forever and won't need to re-parse
// them; some (eg. SCIP, Gurobi) need to re-parse the parameters every time
// they do Solve(). We just store the parameters string anyway.
string extension = interface_->ValidFileExtensionForParameterFile();
int32 tid = static_cast<int32>(pthread_self());
int32 pid = static_cast<int32>(getpid());
int64 now = WallTimer::GetTimeInMicroSeconds();
string filename = StringPrintf("/tmp/parameters-tempfile-%x-%d-%llx%s",
tid, pid, now, extension.c_str());
bool no_error_so_far = true;
if (no_error_so_far) {
no_error_so_far =
file::SetContents(filename, parameters, file::Defaults()).ok();
}
if (no_error_so_far) {
no_error_so_far = interface_->ReadParameterFile(filename);
// We need to clean up the file even if ReadParameterFile() returned
// false. In production we can continue even if the deletion failed.
if (!File::Delete(filename.c_str())) {
LOG(DFATAL) << "Couldn't delete temporary parameters file: "
<< filename;
}
}
if (!no_error_so_far) {
LOG(WARNING) << "Error in SetSolverSpecificParametersAsString() "
<< "for solver type: "
<< MPModelRequest::OptimizationProblemType_Name(
static_cast<MPModelRequest::OptimizationProblemType>(
ProblemType()));
}
return no_error_so_far;
}
// ----- Solver -----
#if defined(USE_CLP) || defined(USE_CBC)
@@ -327,7 +368,7 @@ extern MPSolverInterface* BuildSCIPInterface(MPSolver* const solver);
extern MPSolverInterface* BuildSLMInterface(MPSolver* const solver, bool mip);
#endif
#if defined(USE_GUROBI)
extern MPSolverInterface* BuildGurobiInterface(bool mip, MPSolver* const solver);
extern MPSolverInterface* BuildGurobiInterface(MPSolver* const solver, bool mip);
#endif
@@ -361,9 +402,9 @@ MPSolverInterface* BuildSolverInterface(MPSolver* const solver) {
#endif
#if defined(USE_GUROBI)
case MPSolver::GUROBI_LINEAR_PROGRAMMING:
return BuildGurobiInterface(false, solver);
return BuildGurobiInterface(solver, false);
case MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING:
return BuildGurobiInterface(true, solver);
return BuildGurobiInterface(solver, true);
#endif
default:
// TODO(user): Revert to the best *available* interface.
@@ -373,13 +414,30 @@ MPSolverInterface* BuildSolverInterface(MPSolver* const solver) {
}
} // namespace
namespace {
int NumDigits(int n) {
// Number of digits needed to write a non-negative integer in base 10.
// Note(user): max(1, log(0) + 1) == max(1, -inf) == 1.
#if defined(_MSC_VER)
return static_cast<int>(std::max(1.0, log(1.0L * n) / log(10.0L) + 1.0));
#else
return static_cast<int>(std::max(1.0, log10(n) + 1));
#endif
}
} // namespace
MPSolver::MPSolver(const string& name, OptimizationProblemType problem_type)
: name_(name),
problem_type_(problem_type),
variable_name_to_index_(1),
constraint_name_to_index_(1),
time_limit_(0.0),
write_model_filename_("") {
var_and_constraint_names_allow_export_(true) {
timer_.Restart();
interface_.reset(BuildSolverInterface(this));
if (FLAGS_linear_solver_enable_verbose_output) {
EnableOutput();
}
objective_.reset(new MPObjective(interface_.get()));
}
@@ -403,64 +461,27 @@ MPConstraint* MPSolver::LookupConstraintOrNull(
return constraints_[it->second];
}
// ----- Names management -----
bool MPSolver::CheckNameValidity(const string& name) {
// CheckNameValidity() is an internal method, and the following test is about
// its usage -- we check that it is never *called* on an empty name.
if (name.empty()) {
LOG(DFATAL) << "CheckNameValidity() should not be passed an empty name.";
}
// Allow names that conform to the LP and MPS format.
const int kMaxNameLength = 255;
if (name.size() > kMaxNameLength) {
LOG(WARNING) << "Invalid name " << name
<< ": length > " << kMaxNameLength << "."
<< " Will be unable to write model to file.";
return false;
}
if (name.find_first_of(" +-*<>=:\\") != string::npos) {
LOG(WARNING) << "Invalid name " << name
<< ": contains forbidden character: +-*<>=:\\ space."
<< " Will be unable to write model to file.";
return false;
}
size_t first_occurrence = name.find_first_of(".0123456789");
if (first_occurrence != string::npos && first_occurrence == 0) {
LOG(WARNING) << "Invalid name " << name
<< ": first character should not be . or a number."
<< " Will be unable to write model to file.";
return false;
}
return true;
}
bool MPSolver::CheckAllNamesValidity() {
for (int i = 0; i < variables_.size(); ++i) {
if (!CheckNameValidity(variables_[i]->name())) {
return false;
}
}
for (int i = 0; i < constraints_.size(); ++i) {
if (!CheckNameValidity(constraints_[i]->name())) {
return false;
}
}
return true;
}
// ----- Methods using protocol buffers -----
MPSolver::LoadStatus MPSolver::LoadModel(const MPModelProto& input_model) {
// TODO(user): clear the previous data in the solver, and re-use the
// existing hash maps!
hash_map<string, MPVariable*> variables;
for (int i = 0; i < input_model.variables_size(); ++i) {
const MPVariableProto& var_proto = input_model.variables(i);
const string& id = var_proto.id();
if (!ContainsKey(variables, id)) {
if (var_and_constraint_names_allow_export_) {
// TODO(user): unit test this clause and the similar one below.
var_and_constraint_names_allow_export_ &=
MPModelProtoExporter::CheckNameValidity(id);
}
MPVariable* variable = MakeNumVar(var_proto.lb(), var_proto.ub(), id);
variable->SetInteger(var_proto.integer());
variables[id] = variable;
} else {
LOG(ERROR) << "Multiple definitions of the same variable: '" << id
<< "', model '" << input_model.name() << "'";
return MPSolver::DUPLICATE_VARIABLE_ID;
}
}
@@ -469,7 +490,11 @@ MPSolver::LoadStatus MPSolver::LoadModel(const MPModelProto& input_model) {
for (int i = 0; i < input_model.constraints_size(); ++i) {
tmp_variable_set.clear();
const MPConstraintProto& ct_proto = input_model.constraints(i);
const string& ct_id = ct_proto.has_id() ? ct_proto.id() : "";
const string& ct_id = ct_proto.id();
if (var_and_constraint_names_allow_export_) {
var_and_constraint_names_allow_export_ &=
MPModelProtoExporter::CheckNameValidity(ct_id);
}
MPConstraint* const ct = MakeRowConstraint(ct_proto.lb(),
ct_proto.ub(),
ct_id);
@@ -515,8 +540,9 @@ MPSolver::LoadStatus MPSolver::LoadModel(const MPModelProto& input_model) {
return MPSolver::NO_ERROR;
}
void MPSolver::FillSolutionResponse(MPSolutionResponse* response) const {
DCHECK(response != NULL);
CHECK_NOTNULL(response);
if ((response->has_result_status() &&
response->result_status() != MPSolutionResponse::NOT_SOLVED) ||
response->has_objective_value() ||
@@ -573,10 +599,11 @@ void MPSolver::FillSolutionResponse(MPSolutionResponse* response) const {
}
}
// static
void MPSolver::SolveWithProtocolBuffers(const MPModelRequest& model_request,
MPSolutionResponse* response) {
DCHECK(response != NULL);
CHECK_NOTNULL(response);
const MPModelProto& model = model_request.model();
MPSolver solver(model.name(),
static_cast<MPSolver::OptimizationProblemType>(
@@ -584,8 +611,9 @@ void MPSolver::SolveWithProtocolBuffers(const MPModelRequest& model_request,
const MPSolver::LoadStatus loadStatus = solver.LoadModel(model);
if (loadStatus != MPSolver::NO_ERROR) {
LOG(WARNING) << "Loading model from protocol buffer failed, "
<< "load status = " << loadStatus;
<< " (" << loadStatus << ")";
response->set_result_status(MPSolutionResponse::ABNORMAL);
return;
}
if (model_request.has_time_limit_ms()) {
@@ -595,6 +623,41 @@ void MPSolver::SolveWithProtocolBuffers(const MPModelRequest& model_request,
solver.FillSolutionResponse(response);
}
namespace {
// Outputs the terms in var_coeff_map to output_term, by sorting the
// variables by their indices as returned by the var_name_to_index map.
void OutputTermsToProto(
const std::vector<MPVariable*>& variables,
const hash_map<const MPVariable*, int>& var_name_to_index,
const CoeffMap& var_coeff_map,
google::protobuf::RepeatedPtrField<MPTermProto>* output_terms) {
// Vector linear_term will contain pairs (variable index, coeff), that will
// be sorted by variable index.
std::vector<pair<int, double> > linear_term;
for (CoeffEntry entry : var_coeff_map) {
const MPVariable* const var = entry.first;
const double coef = entry.second;
const int var_index = FindWithDefault(var_name_to_index, var, -1);
DCHECK_NE(-1, var_index);
linear_term.push_back(pair<int, double>(var_index, coef));
}
// The cost of sort is expected to be low as constraints usually have very
// few terms.
sort(linear_term.begin(), linear_term.end());
// Now use linear term.
for (int k = 0; k < linear_term.size(); ++k) {
const pair<int, double>& p = linear_term[k];
const int var_index = p.first;
const double coef = p.second;
const MPVariable* const var = variables[var_index];
MPTermProto* term_proto = output_terms->Add();
term_proto->set_variable_id(var->name());
term_proto->set_coefficient(coef);
}
}
} // namespace
void MPSolver::ExportModel(MPModelProto* output_model) const {
DCHECK(output_model != NULL);
if (output_model->variables_size() > 0 ||
@@ -612,6 +675,8 @@ void MPSolver::ExportModel(MPModelProto* output_model) const {
output_model->clear_name();
}
// Name
output_model->set_name(Name());
// Variables
for (int j = 0; j < variables_.size(); ++j) {
const MPVariable* const var = variables_[j];
@@ -623,6 +688,17 @@ void MPSolver::ExportModel(MPModelProto* output_model) const {
variable_proto->set_integer(var->integer());
}
// Map the variables to their indices. This is needed to output the
// variables in the order they were created, which in turn is needed to have
// repeatable results with ExportModelAsLpString and ExportModelAsMpsString.
// This step is needed as long as the variable indices are given by the
// underlying solver at the time of model extraction.
// TODO(user): remove this step.
hash_map<const MPVariable*, int> var_name_to_index;
for (int j = 0; j < variables_.size(); ++j) {
var_name_to_index[variables_[j]] = j;
}
// Constraints
for (int i = 0; i < constraints_.size(); ++i) {
MPConstraint* const constraint = constraints_[i];
@@ -632,28 +708,13 @@ void MPSolver::ExportModel(MPModelProto* output_model) const {
constraint_proto->set_id(constraint->name());
constraint_proto->set_lb(constraint->lb());
constraint_proto->set_ub(constraint->ub());
for (ConstIter<hash_map<const MPVariable*, double> >
it(constraint->coefficients_);
!it.at_end(); ++it) {
const MPVariable* const var = it->first;
const double coef = it->second;
MPTermProto* const term = constraint_proto->add_terms();
term->set_variable_id(var->name());
term->set_coefficient(coef);
}
OutputTermsToProto(variables_, var_name_to_index, constraint->coefficients_,
constraint_proto->mutable_terms());
}
// Objective
for (hash_map<const MPVariable*, double>::const_iterator it =
objective_->coefficients_.begin();
it != objective_->coefficients_.end();
++it) {
const MPVariable* const var = it->first;
const double coef = it->second;
MPTermProto* const term = output_model->add_objective_terms();
term->set_variable_id(var->name());
term->set_coefficient(coef);
}
// Objective.
OutputTermsToProto(variables_, var_name_to_index, objective_->coefficients_,
output_model->mutable_objective_terms());
output_model->set_maximize(Objective().maximization());
output_model->set_objective_offset(Objective().offset());
}
@@ -757,8 +818,11 @@ MPVariable* MPSolver::MakeVar(
double lb, double ub, bool integer, const string& name) {
const int var_index = NumVariables();
const string fixed_name = name.empty()
? StringPrintf("auto_variable_%06d", var_index) : name;
CheckNameValidity(fixed_name);
? StringPrintf("auto_v_%09d", var_index) : name;
if (var_and_constraint_names_allow_export_) {
var_and_constraint_names_allow_export_ &=
MPModelProtoExporter::CheckNameValidity(fixed_name);
}
InsertOrDie(&variable_name_to_index_, fixed_name, var_index);
MPVariable* v = new MPVariable(lb, ub, integer, fixed_name, interface_.get());
variables_.push_back(v);
@@ -837,8 +901,11 @@ MPConstraint* MPSolver::MakeRowConstraint(double lb, double ub,
const string& name) {
const int constraint_index = NumConstraints();
const string fixed_name = name.empty()
? StringPrintf("auto_constraint_%06d", constraint_index) : name;
CheckNameValidity(fixed_name);
? StringPrintf("auto_c_%09d", constraint_index) : name;
if (var_and_constraint_names_allow_export_) {
var_and_constraint_names_allow_export_ &=
MPModelProtoExporter::CheckNameValidity(fixed_name);
}
InsertOrDie(&constraint_name_to_index_, fixed_name, constraint_index);
MPConstraint* const constraint =
new MPConstraint(lb, ub, fixed_name, interface_.get());
@@ -892,6 +959,7 @@ MPSolver::ResultStatus MPSolver::Solve(const MPSolverParameters &param) {
return interface_->result_status_;
}
const MPSolver::ResultStatus status = interface_->Solve(param);
if (!FLAGS_verify_solution) return status;
if (status != MPSolver::OPTIMAL) {
@@ -902,8 +970,7 @@ MPSolver::ResultStatus MPSolver::Solve(const MPSolverParameters &param) {
}
if (VerifySolution(
param.GetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE),
FLAGS_log_verification_errors,
NULL)) {
FLAGS_log_verification_errors)) {
return status;
} else {
return MPSolver::ABNORMAL;
@@ -974,11 +1041,9 @@ string PrettyPrintConstraint(const MPConstraint& constraint) {
} // namespace
// TODO(user): split.
bool MPSolver::VerifySolution(double max_absolute_error,
bool log_errors,
double* observed_max_absolute_error) const {
bool MPSolver::VerifySolution(double tolerance, bool log_errors) const {
double max_observed_error = 0;
if (max_absolute_error < 0) max_absolute_error = infinity();
if (tolerance < 0) tolerance = infinity();
int num_errors = 0;
// Verify variables.
@@ -992,12 +1057,9 @@ bool MPSolver::VerifySolution(double max_absolute_error,
LOG_IF(ERROR, log_errors) << "NaN value for " << PrettyPrintVar(var);
continue;
}
if (var.lb() <= -infinity() && var.ub() >= infinity()) {
continue;
}
// Check lower bound.
if (var.lb() != -infinity()) {
if (value < var.lb() - max_absolute_error) {
if (value < var.lb() - tolerance) {
++num_errors;
max_observed_error = std::max(max_observed_error, var.lb() - value);
LOG_IF(ERROR, log_errors)
@@ -1006,7 +1068,7 @@ bool MPSolver::VerifySolution(double max_absolute_error,
}
// Check upper bound.
if (var.ub() != infinity()) {
if (value > var.ub() + max_absolute_error) {
if (value > var.ub() + tolerance) {
++num_errors;
max_observed_error = std::max(max_observed_error, value - var.ub());
LOG_IF(ERROR, log_errors)
@@ -1015,7 +1077,7 @@ bool MPSolver::VerifySolution(double max_absolute_error,
}
// Check integrality.
if (var.integer()) {
if (fabs(value - round(value)) > max_absolute_error) {
if (fabs(value - round(value)) > tolerance) {
++num_errors;
max_observed_error = std::max(max_observed_error,
fabs(value - round(value)));
@@ -1047,21 +1109,16 @@ bool MPSolver::VerifySolution(double max_absolute_error,
<< "NaN value for " << PrettyPrintConstraint(constraint);
continue;
}
// This shouldn't happen normally, but the client could have tweaked the
// model in a weird way. Who knows.
if (constraint.lb() <= -infinity() && constraint.ub() >= infinity()) {
continue;
}
// Check bounds.
if (constraint.lb() != -infinity()) {
if (activity < constraint.lb() - max_absolute_error) {
if (activity < constraint.lb() - tolerance) {
++num_errors;
max_observed_error = std::max(max_observed_error,
constraint.lb() - activity);
LOG_IF(ERROR, log_errors)
<< "Activity " << activity << " too low for "
<< PrettyPrintConstraint(constraint);
} else if (inaccurate_activity < constraint.lb() - max_absolute_error) {
} else if (inaccurate_activity < constraint.lb() - tolerance) {
LOG_IF(WARNING, log_errors)
<< "Activity " << activity << ", computed with the (inaccurate)"
<< " standard sum of its terms, is too low for "
@@ -1069,14 +1126,14 @@ bool MPSolver::VerifySolution(double max_absolute_error,
}
}
if (constraint.ub() != -infinity()) {
if (activity > constraint.ub() + max_absolute_error) {
if (activity > constraint.ub() + tolerance) {
++num_errors;
max_observed_error = std::max(max_observed_error,
activity - constraint.ub());
LOG_IF(ERROR, log_errors)
<< "Activity " << activity << " too high for "
<< PrettyPrintConstraint(constraint);
} else if (inaccurate_activity > constraint.ub() + max_absolute_error) {
} else if (inaccurate_activity > constraint.ub() + tolerance) {
LOG_IF(WARNING, log_errors)
<< "Activity " << activity << ", computed with the (inaccurate)"
<< " standard sum of its terms, is too high for "
@@ -1088,6 +1145,7 @@ bool MPSolver::VerifySolution(double max_absolute_error,
// Verify that the objective value wasn't reported incorrectly.
const MPObjective& objective = Objective();
AccurateSum<double> objective_sum;
objective_sum.Add(objective.offset());
double inaccurate_objective_value = objective.offset();
for (hash_map<const MPVariable*, double>::const_iterator
it = objective.coefficients_.begin();
@@ -1096,30 +1154,31 @@ bool MPSolver::VerifySolution(double max_absolute_error,
objective_sum.Add(term);
inaccurate_objective_value += term;
}
objective_sum.Add(objective.offset());
const double actual_objective_value = objective_sum.Value();
if (fabs(actual_objective_value - objective.Value()) > max_absolute_error) {
if (!AreWithinAbsoluteOrRelativeTolerances(objective.Value(),
actual_objective_value,
tolerance,
tolerance)) {
++num_errors;
max_observed_error = std::max(
max_observed_error, fabs(actual_objective_value - objective.Value()));
max_observed_error, fabs(actual_objective_value - objective.Value()));
LOG_IF(ERROR, log_errors)
<< "Objective value " << objective.Value() << " isn't accurate"
<< ", it should be " << actual_objective_value
<< " (delta=" << actual_objective_value - objective.Value() << ").";
} else if (fabs(inaccurate_objective_value - objective.Value()) >
max_absolute_error) {
} else if (!AreWithinAbsoluteOrRelativeTolerances(objective.Value(),
inaccurate_objective_value,
tolerance,
tolerance)) {
LOG_IF(WARNING, log_errors)
<< "Objective value " << objective.Value() << " doesn't correspond"
<< " to the value computed with the standard (and therefore inaccurate)"
<< " sum of its terms.";
}
if (observed_max_absolute_error != NULL) {
*observed_max_absolute_error = max_observed_error;
}
if (num_errors > 0) {
LOG_IF(ERROR, log_errors)
<< "There were " << num_errors << " errors above the threshold ("
<< max_absolute_error << "), the largest was " << max_observed_error;
<< "There were " << num_errors << " errors above the tolerance ("
<< tolerance << "), the largest was " << max_observed_error;
return false;
}
return true;
@@ -1148,6 +1207,22 @@ bool MPSolver::OwnsVariable(const MPVariable* var) const {
return variables_[var_index] == var;
}
bool MPSolver::ExportModelAsLpFormat(bool obfuscate, string* output) {
MPModelProto proto;
ExportModel(&proto);
MPModelProtoExporter exporter(proto);
return exporter.ExportModelAsLpFormat(obfuscate, output);
}
bool MPSolver::ExportModelAsMpsFormat(
bool fixed_format, bool obfuscate, string* output) {
MPModelProto proto;
ExportModel(&proto);
MPModelProtoExporter exporter(proto);
return exporter.ExportModelAsMpsFormat(fixed_format, obfuscate, output);
}
// ---------- MPSolverInterface ----------
const int MPSolverInterface::kDummyVariableIndex = 0;
@@ -1160,25 +1235,6 @@ MPSolverInterface::MPSolverInterface(MPSolver* const solver)
MPSolverInterface::~MPSolverInterface() {}
void MPSolverInterface::WriteModelToPredefinedFiles() {
// TODO(user): make this method return a boolean.
if (!FLAGS_solver_write_model.empty()) {
if (!solver_->CheckAllNamesValidity()) {
LOG(DFATAL) << "Invalid name. Unable to write model to file";
return;
}
WriteModel(FLAGS_solver_write_model);
}
const string filename = solver_->write_model_filename();
if (!filename.empty()) {
if (!solver_->CheckAllNamesValidity()) {
LOG(DFATAL) << "Invalid name. Unable to write model to file";
return;
}
WriteModel(filename);
}
}
void MPSolverInterface::ExtractModel() {
switch (sync_status_) {
case MUST_RELOAD: {
@@ -1319,6 +1375,15 @@ void MPSolverInterface::SetIntegerParamToUnsupportedValue(
<< " to an unsupported value: " << value;
}
bool MPSolverInterface::ReadParameterFile(const string& filename) {
LOG(WARNING) << "ReadParameterFile() not supported by this solver.";
return false;
}
string MPSolverInterface::ValidFileExtensionForParameterFile() const {
return ".tmp";
}
// ---------- MPSolverParameters ----------
const double MPSolverParameters::kDefaultRelativeMipGap = 1e-4;
@@ -1510,3 +1575,4 @@ int MPSolverParameters::GetIntegerParam(
} // namespace operations_research

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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
@@ -163,6 +163,7 @@ class MPSolverInterface;
class MPSolverParameters;
class MPVariable;
// This mathematical programming (MP) solver class is the main class
// though which users build and solve problems.
class MPSolver {
@@ -321,23 +322,26 @@ class MPSolver {
ResultStatus Solve(const MPSolverParameters& param);
// Advanced usage:
// Verifies the *correctness* of solution: all variables must be within
// their domain, all constraints must be satisfied, and the reported
// Verifies the *correctness* of the solution: all variables must be within
// their domains, all constraints must be satisfied, and the reported
// objective value must be accurate.
// Usage:
// - This can only be called after Solve() was called.
// - If "max_absolute_error" is negative, it will be set to infinity().
// - "tolerance" is interpreted as an absolute error threshold.
// - For the objective value only, if the absolute error is too large,
// the tolerance is interpreted as a relative error threshold instead.
// - If "log_errors" is true, every single violation will be logged.
// - The observer maximum absolute error is output if
// "observed_max_absolute_error" is not NULL.
// - If "tolerance" is negative, it will be set to infinity().
//
// Most users should just set the --verify_solution flag and not bother
// using this method directly.
bool VerifySolution(double max_absolute_error,
bool log_errors,
double* observed_max_absolute_error) const;
bool VerifySolution(double tolerance, bool log_errors) const;
// Advanced usage: resets extracted model to solve from scratch.
// Advanced usage: resets extracted model to solve from scratch. This won't
// reset the parameters that were set with
// SetSolverSpecificParametersAsString() or set_time_limit() or even clear the
// linear program. It will just make sure that next Solve() will be as if
// everything was reconstructed from scratch.
void Reset();
// ----- Methods using protocol buffers -----
@@ -367,6 +371,7 @@ class MPSolver {
static void SolveWithProtocolBuffers(const MPModelRequest& model_request,
MPSolutionResponse* response);
// Exports model to protocol buffer.
void ExportModel(MPModelProto* output_model) const;
@@ -400,8 +405,25 @@ class MPSolver {
// VerifySolution() for that.
bool LoadSolutionFromProto(const MPSolutionResponse& response);
// ----- Export model to files or strings -----
// Shortcuts to the homonymous MPModelProtoExporter methods, via
// exporting to a MPModelProto with ExportModel() (see above).
bool ExportModelAsLpFormat(bool obfuscated, string* model_str);
bool ExportModelAsMpsFormat(
bool fixed_format, bool obfuscated, string* model_str);
// ----- Misc -----
// Advanced usage: pass solver specific parameters in text format. The format
// is solver-specific and is the same as the corresponding solver
// configuration file format. Returns true if the operation was successful.
//
// TODO(user): Currently SCIP will always return true even if the format is
// wrong (you can check the log if you suspect an issue there). This seems to
// be a bug in SCIP though.
bool SetSolverSpecificParametersAsString(const string& parameters);
// Advanced usage: possible basis status values for a variable and the
// slack variable of a linear constraint.
enum BasisStatus {
@@ -426,14 +448,6 @@ class MPSolver {
// stdout.
void EnableOutput();
void set_write_model_filename(const string &filename) {
write_model_filename_ = filename;
}
string write_model_filename() const {
return write_model_filename_;
}
void set_time_limit(int64 time_limit_milliseconds) {
DCHECK_GE(time_limit_milliseconds, 0);
time_limit_ = time_limit_milliseconds;
@@ -456,10 +470,12 @@ class MPSolver {
// discrete problems.
int64 nodes() const;
// Checks the validity of a variable or constraint name.
bool CheckNameValidity(const string& name);
// Checks the validity of all variables and constraints names.
bool CheckAllNamesValidity();
// True iff all variable and constraint names allow exporting the model
// to a file (or as a proto).
// See MPModelProtoExporter::CheckNameValidity() in ./model_exporter.h.
bool var_and_constraint_names_allow_export() {
return var_and_constraint_names_allow_export_;
}
// Returns a string describing the underlying solver and its version.
string SolverVersion() const;
@@ -562,15 +578,24 @@ class MPSolver {
// Time limit in milliseconds (0 = no limit).
int64 time_limit_;
// Name of the file where the solver writes out the model when Solve
// is called. If empty, no file is written.
string write_model_filename_;
// See the homonymous getter above.
bool var_and_constraint_names_allow_export_;
WallTimer timer_;
// Permanent storage for SetSolverSpecificParametersAsString().
string solver_specific_parameter_string_;
DISALLOW_COPY_AND_ASSIGN(MPSolver);
};
// 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_) { ... }
typedef hash_map<const MPVariable*, double> CoeffMap;
typedef std::pair<const MPVariable*, double> CoeffEntry;
// A class to express a linear objective.
class MPObjective {
public:
@@ -633,12 +658,12 @@ class MPObjective {
// At construction, an MPObjective has no terms (which is equivalent
// on having a coefficient of 0 for all variables), and an offset of 0.
explicit MPObjective(MPSolverInterface* const interface)
: interface_(interface), offset_(0.0) {}
: interface_(interface), coefficients_(1), offset_(0.0) {}
MPSolverInterface* const interface_;
// Mapping var -> coefficient.
hash_map<const MPVariable*, double> coefficients_;
CoeffMap coefficients_;
// Constant term.
double offset_;
@@ -744,6 +769,18 @@ class MPConstraint {
// Sets both the lower and upper bounds.
void SetBounds(double lb, double ub);
// Advanced usage: returns true if the constraint is "lazy" (see below).
bool is_lazy() const { return is_lazy_; }
// Advanced usage: sets the constraint "laziness".
// *** This is only supported for SCIP and has no effect on other solvers. ***
// When 'laziness' is true, the constraint is only considered by the Linear
// Programming solver if its current solution violates the constraint.
// In this case, the constraint is definitively added to the problem.
// This may be useful in some MIP problems, and may have a dramatic impact
// on performance.
// For more info see: http://tinyurl.com/lazy-constraints.
void set_is_lazy(bool laziness) { is_lazy_ = laziness; }
// Returns the constraint's activity in the current solution:
// sum over all terms of (coefficient * variable value)
double activity() const;
@@ -778,8 +815,8 @@ class MPConstraint {
double ub,
const string& name,
MPSolverInterface* const interface)
: lb_(lb), ub_(ub), name_(name), index_(-1), dual_value_(0.0),
activity_(0.0), interface_(interface) {}
: coefficients_(1), lb_(lb), ub_(ub), name_(name), is_lazy_(false),
index_(-1), dual_value_(0.0), activity_(0.0), interface_(interface) {}
void set_index(int index) { index_ = index; }
void set_activity(double activity) { activity_ = activity; }
@@ -791,14 +828,21 @@ class MPConstraint {
bool ContainsNewVariables();
// Mapping var -> coefficient.
hash_map<const MPVariable*, double> coefficients_;
CoeffMap coefficients_;
// The lower bound for the linear constraint.
double lb_;
// The upper bound for the linear constraint.
double ub_;
// Name.
const string name_;
// True if the constraint is "lazy", i.e. the constraint is added to the
// underlying Linear Programming solver only if it is violated.
// By default this parameter is 'false'.
bool is_lazy_;
int index_;
double dual_value_;
double activity_;
@@ -807,6 +851,7 @@ class MPConstraint {
};
// This class stores parameter settings for LP and MIP solvers.
// Some parameters are marked as advanced: do not change their values
// unless you know what you are doing!
@@ -998,8 +1043,7 @@ class MPSolverInterface {
// ----- Solve -----
// Solves problem with specified parameter values. Returns true if the
// solution is optimal. Calls WriteModelToPredefinedFiles
// to allow the user to write the model to a file.
// solution is optimal.
virtual MPSolver::ResultStatus Solve(const MPSolverParameters& param) = 0;
// ----- Model modifications and extraction -----
@@ -1080,9 +1124,6 @@ class MPSolverInterface {
virtual bool CheckBestObjectiveBoundExists() const;
// ----- Misc -----
// Writes model to a file.
virtual void WriteModel(const string& filename) = 0;
// Queries problem type. For simplicity, the distinction between
// continuous and discrete is based on the declaration of the user
// when the solver is created (example: GLPK_LINEAR_PROGRAMMING
@@ -1156,16 +1197,6 @@ class MPSolverInterface {
// objective offset.
static const int kDummyVariableIndex;
// Writes out the model to a file specified by the
// --solver_write_model command line argument or
// MPSolver::set_write_model_filename.
// The file is written by each solver interface (CBC, CLP, GLPK, SCIP) and
// each behaves a little differently.
// If filename ends in ".lp", then the file is written in the
// LP format (except for the CLP solver that does not support the LP
// format). In all other cases it is written in the MPS format.
void WriteModelToPredefinedFiles();
// Extracts model stored in MPSolver.
void ExtractModel();
// Extracts the variables that have not been extracted yet.
@@ -1202,6 +1233,15 @@ class MPSolverInterface {
virtual void SetDualTolerance(double value) = 0;
virtual void SetPresolveMode(int value) = 0;
// Reads a solver-specific file of parameters and set them.
// Returns true if there was no errors.
virtual bool ReadParameterFile(const string& filename);
// Returns a file extension like ".tmp", this is needed because some solvers
// require a given extension for the ReadParameterFile() filename and we need
// to know it to generate a temporary parameter file.
virtual string ValidFileExtensionForParameterFile() const;
// Sets the scaling mode.
virtual void SetScalingMode(int value) = 0;
virtual void SetLpAlgorithm(int value) = 0;

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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
@@ -13,6 +13,8 @@
%include base/base.swig
%include util/data.swig
// Swig doesn't like module initializers.
#define DECLARE_MODULE_INITIALIZER(x);
// Include the file we want to wrap a first time.
%{
@@ -372,9 +374,29 @@ class LinearConstraint(object):
# See ../python/linear_programming.py for example on how to use the nice
# extended python API provided below.
%extend MPSolver {
string ExportModelAsLpFormat(bool obfuscated) {
string output;
if (!self->ExportModelAsLpFormat(obfuscated, &output)) return "";
return output;
}
string ExportModelAsMpsFormat(bool fixed_format, bool obfuscated) {
string output;
if (!self->ExportModelAsMpsFormat(fixed_format, obfuscated, &output)) {
return "";
}
return output;
}
%pythoncode {
def Add(self, constraint, name=''):
return constraint.Extract(self, name)
if isinstance(constraint, bool):
if constraint:
return self.RowConstraint(0, 0, name)
else:
return self.RowConstraint(1, 1, name)
else:
return constraint.Extract(self, name)
def Sum(self, expr_array):
result = SumArray(expr_array)
@@ -435,6 +457,8 @@ namespace operations_research {
%rename (setCoefficient) MPConstraint::SetCoefficient;
%rename (setLb) MPConstraint::SetLB;
%rename (setUb) MPConstraint::SetUB;
%rename (setIsLazy) MPConstraint::set_is_lazy;
%rename (isLazy) MPConstraint::is_lazy;
// Rename rules on MPObjective.
%rename (addOffset) MPObjective::AddOffset;

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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
@@ -107,9 +107,6 @@ class SCIPInterface : public MPSolverInterface {
}
// ----- Misc -----
// Write model
virtual void WriteModel(const string& filename);
// Query problem type.
virtual bool IsContinuous() const { return false; }
virtual bool IsLP() const { return false; }
@@ -140,6 +137,9 @@ class SCIPInterface : public MPSolverInterface {
virtual void SetScalingMode(int value);
virtual void SetLpAlgorithm(int value);
virtual bool ReadParameterFile(const string& filename);
virtual string ValidFileExtensionForParameterFile() const;
void CreateSCIP();
void DeleteSCIP();
@@ -201,21 +201,6 @@ void SCIPInterface::DeleteSCIP() {
scip_ = NULL;
}
void SCIPInterface::WriteModel(const string& filename) {
#if (SCIP_VERSION < 300)
// The message handler also controls how the model is written to a file.
// If it is NULL, then nothing is written to the file.
ORTOOLS_SCIP_CALL(SCIPsetDefaultMessagehdlr());
#endif
ORTOOLS_SCIP_CALL(SCIPwriteOrigProblem(scip_, filename.c_str(), NULL, false));
// Restore mesage handler to its original value.
#if (SCIP_VERSION < 300)
if (quiet_) {
ORTOOLS_SCIP_CALL(SCIPsetMessagehdlr(NULL));
}
#endif
}
// ------ Model modifications and extraction -----
// Not cached
@@ -504,8 +489,6 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
ExtractModel();
VLOG(1) << StringPrintf("Model built in %.3f seconds.", timer.Get());
WriteModelToPredefinedFiles();
// Time limit.
if (solver_->time_limit()) {
VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
@@ -515,6 +498,10 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
ORTOOLS_SCIP_CALL(SCIPresetParam(scip_, "limits/time"));
}
// TODO(user): clarify the differences and the precedence between the two
// SetParameter*() API (file-based and generic, parameter-based).
solver_->SetSolverSpecificParametersAsString(
solver_->solver_specific_parameter_string_);
SetParameters(param);
// Solve.
@@ -678,6 +665,14 @@ void SCIPInterface::SetLpAlgorithm(int value) {
}
}
bool SCIPInterface::ReadParameterFile(const string& filename) {
return SCIPreadParams(scip_, filename.c_str()) == SCIP_OKAY;
}
string SCIPInterface::ValidFileExtensionForParameterFile() const {
return ".set";
}
MPSolverInterface* BuildSCIPInterface(MPSolver* const solver) {
return new SCIPInterface(solver);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2010-2012 Google
// Copyright 2010-2013 Google
// 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