new protobuf to store linear models and solutions, much smaller and faster

This commit is contained in:
lperron@google.com
2013-12-10 15:50:16 +00:00
parent 85fc9db45b
commit cfc3a58f80
6 changed files with 359 additions and 150 deletions

View File

@@ -474,7 +474,9 @@ LINEAR_SOLVER_LIB_OBJS = \
$(OBJ_DIR)/gurobi_interface.$O \
$(OBJ_DIR)/linear_solver.$O \
$(OBJ_DIR)/linear_solver.pb.$O \
$(OBJ_DIR)/linear_solver2.pb.$O \
$(OBJ_DIR)/model_exporter.$O \
$(OBJ_DIR)/proto_tools.$O \
$(OBJ_DIR)/scip_interface.$O \
$(OBJ_DIR)/sulum_interface.$O
@@ -491,7 +493,7 @@ $(OBJ_DIR)/glpk_interface.$O:$(SRC_DIR)/linear_solver/glpk_interface.cc
$(OBJ_DIR)/gurobi_interface.$O:$(SRC_DIR)/linear_solver/gurobi_interface.cc
$(CCC) $(CFLAGS) -c $(SRC_DIR)/linear_solver/gurobi_interface.cc $(OBJ_OUT)$(OBJ_DIR)$Sgurobi_interface.$O
$(OBJ_DIR)/linear_solver.$O:$(SRC_DIR)/linear_solver/linear_solver.cc $(GEN_DIR)/linear_solver/linear_solver.pb.h
$(OBJ_DIR)/linear_solver.$O:$(SRC_DIR)/linear_solver/linear_solver.cc $(GEN_DIR)/linear_solver/linear_solver.pb.h $(GEN_DIR)/linear_solver/linear_solver2.pb.h
$(CCC) $(CFLAGS) -c $(SRC_DIR)/linear_solver/linear_solver.cc $(OBJ_OUT)$(OBJ_DIR)$Slinear_solver.$O
$(OBJ_DIR)/linear_solver.pb.$O:$(GEN_DIR)/linear_solver/linear_solver.pb.cc
@@ -502,9 +504,20 @@ $(GEN_DIR)/linear_solver/linear_solver.pb.cc:$(SRC_DIR)/linear_solver/linear_sol
$(GEN_DIR)/linear_solver/linear_solver.pb.h:$(GEN_DIR)/linear_solver/linear_solver.pb.cc
$(OBJ_DIR)/linear_solver2.pb.$O:$(GEN_DIR)/linear_solver/linear_solver2.pb.cc
$(CCC) $(CFLAGS) -c $(GEN_DIR)/linear_solver/linear_solver2.pb.cc $(OBJ_OUT)$(OBJ_DIR)$Slinear_solver2.pb.$O
$(GEN_DIR)/linear_solver/linear_solver2.pb.cc:$(SRC_DIR)/linear_solver/linear_solver2.proto
$(PROTOBUF_DIR)/bin/protoc --proto_path=$(INC_DIR) --cpp_out=$(GEN_DIR) $(SRC_DIR)/linear_solver/linear_solver2.proto
$(GEN_DIR)/linear_solver/linear_solver2.pb.h:$(GEN_DIR)/linear_solver/linear_solver2.pb.cc
$(OBJ_DIR)/model_exporter.$O:$(SRC_DIR)/linear_solver/model_exporter.cc $(GEN_DIR)/linear_solver/linear_solver.pb.h
$(CCC) $(CFLAGS) -c $(SRC_DIR)/linear_solver/model_exporter.cc $(OBJ_OUT)$(OBJ_DIR)$Smodel_exporter.$O
$(OBJ_DIR)/proto_tools.$O:$(SRC_DIR)/linear_solver/proto_tools.cc $(GEN_DIR)/linear_solver/linear_solver.pb.h $(GEN_DIR)/linear_solver/linear_solver2.pb.h
$(CCC) $(CFLAGS) -c $(SRC_DIR)/linear_solver/proto_tools.cc $(OBJ_OUT)$(OBJ_DIR)$Sproto_tools.$O
$(OBJ_DIR)/scip_interface.$O:$(SRC_DIR)/linear_solver/scip_interface.cc
$(CCC) $(CFLAGS) -c $(SRC_DIR)/linear_solver/scip_interface.cc $(OBJ_OUT)$(OBJ_DIR)$Sscip_interface.$O

View File

@@ -35,18 +35,17 @@
#if defined(USE_GLPK)
extern "C" {
#include "glpk.h"
#include "glpk.h"
}
DECLARE_double(solver_timeout_in_seconds);
DECLARE_string(solver_write_model);
namespace operations_research {
// Class to store information gathered in the callback
class GLPKInformation {
public:
explicit GLPKInformation(bool maximize): num_all_nodes_(0) {
explicit GLPKInformation(bool maximize) : num_all_nodes_(0) {
ResetBestObjectiveBound(maximize);
}
void Reset(bool maximize) {
@@ -64,8 +63,6 @@ class GLPKInformation {
double best_objective_bound_;
};
// Function to be called in the GLPK callback
void GLPKGatherInformationCallback(glp_tree* tree, void* info) {
CHECK_NOTNULL(tree);
@@ -122,8 +119,7 @@ class GLPKInterface : public MPSolverInterface {
// Change a coefficient in a constraint.
virtual void SetCoefficient(MPConstraint* const constraint,
const MPVariable* const variable,
double new_value,
double old_value);
double new_value, double old_value);
// Clear a constraint from all its terms.
virtual void ClearConstraint(MPConstraint* const constraint);
// Change a coefficient in the linear objective
@@ -166,9 +162,7 @@ class GLPKInterface : public MPSolverInterface {
return StringPrintf("GLPK %s", glp_version());
}
virtual void* underlying_solver() {
return reinterpret_cast<void*>(lp_);
}
virtual void* underlying_solver() { return reinterpret_cast<void*>(lp_); }
virtual double ComputeExactConditionNumber() const;
@@ -187,8 +181,7 @@ class GLPKInterface : public MPSolverInterface {
virtual void SetLpAlgorithm(int value);
void ExtractOldConstraints();
void ExtractOneConstraint(MPConstraint* const constraint,
int* const indices,
void ExtractOneConstraint(MPConstraint* const constraint, int* const indices,
double* const coefs);
// Transforms basis status from GLPK integer code to MPSolver::BasisStatus.
MPSolver::BasisStatus TransformGLPKBasisStatus(int glpk_basis_status) const;
@@ -196,16 +189,16 @@ class GLPKInterface : public MPSolverInterface {
// Computes the L1-norm of the current scaled basis.
// The L1-norm |A| is defined as max_j sum_i |a_ij|
// This method is available only for continuous problems.
double ComputeScaledBasisL1Norm(
int num_rows, int num_cols,
double* row_scaling_factor, double* column_scaling_factor) const;
double ComputeScaledBasisL1Norm(int num_rows, int num_cols,
double* row_scaling_factor,
double* column_scaling_factor) const;
// Computes the L1-norm of the inverse of the current scaled
// basis.
// This method is available only for continuous problems.
double ComputeInverseScaledBasisL1Norm(
int num_rows, int num_cols,
double* row_scaling_factor, double* column_scaling_factor) const;
double ComputeInverseScaledBasisL1Norm(int num_rows, int num_cols,
double* row_scaling_factor,
double* column_scaling_factor) const;
glp_prob* lp_;
bool mip_;
@@ -281,7 +274,7 @@ void GLPKInterface::SetVariableInteger(int var_index, bool integer) {
if (mip_) {
if (var_index != kNoIndex) {
// Not cached if the variable has been extracted.
glp_set_col_kind(lp_, var_index, integer ? GLP_IV : GLP_CV);
glp_set_col_kind(lp_, var_index, integer ? GLP_IV : GLP_CV);
} else {
sync_status_ = MUST_RELOAD;
}
@@ -316,8 +309,7 @@ void GLPKInterface::SetConstraintBounds(int index, double lb, double ub) {
void GLPKInterface::SetCoefficient(MPConstraint* const constraint,
const MPVariable* const variable,
double new_value,
double old_value) {
double new_value, double old_value) {
InvalidateSolutionSynchronization();
// GLPK does not allow to modify one coefficient at a time, so we
// extract the whole constraint again, if it has been extracted
@@ -405,15 +397,15 @@ void GLPKInterface::ExtractNewVariables() {
// Extract again existing constraints if they contain new variables.
void GLPKInterface::ExtractOldConstraints() {
int max_constraint_size = solver_->ComputeMaxConstraintSize(
0, last_constraint_index_);
int max_constraint_size =
solver_->ComputeMaxConstraintSize(0, last_constraint_index_);
// The first entry in the following arrays is dummy, to be
// consistent with glpk API.
std::unique_ptr<int[]> indices(new int[max_constraint_size + 1]);
std::unique_ptr<double[]> coefs(new double[max_constraint_size + 1]);
for (int i = 0; i < last_constraint_index_; ++i) {
MPConstraint* const ct = solver_->constraints_[i];
MPConstraint* const ct = solver_->constraints_[i];
DCHECK_NE(kNoIndex, ct->index());
const int size = ct->coefficients_.size();
if (size == 0) {
@@ -441,7 +433,7 @@ void GLPKInterface::ExtractOneConstraint(MPConstraint* const constraint,
coefs[k] = entry.second;
++k;
}
glp_set_mat_row(lp_, constraint->index(), k-1, indices, coefs);
glp_set_mat_row(lp_, constraint->index(), k - 1, indices, coefs);
}
// Define new constraints on old and new variables.
@@ -595,15 +587,13 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
if (mip_) {
const double row_activity = glp_mip_row_val(lp_, ct->index());
ct->set_activity(row_activity);
VLOG(4) << "row " << ct->index()
<< ": activity = " << row_activity;
VLOG(4) << "row " << ct->index() << ": activity = " << row_activity;
} else {
const double row_activity = glp_get_row_prim(lp_, ct->index());
ct->set_activity(row_activity);
const double dual_value = glp_get_row_dual(lp_, ct->index());
ct->set_dual_value(dual_value);
VLOG(4) << "row " << ct->index()
<< ": activity = " << row_activity
VLOG(4) << "row " << ct->index() << ": activity = " << row_activity
<< ": dual value = " << dual_value;
}
}
@@ -633,8 +623,7 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
result_status_ = MPSolver::OPTIMAL;
} else if (tmp_status == GLP_FEAS) {
result_status_ = MPSolver::FEASIBLE;
} else if (tmp_status == GLP_NOFEAS ||
tmp_status == GLP_INFEAS) {
} else if (tmp_status == GLP_NOFEAS || tmp_status == GLP_INFEAS) {
// For infeasible problems, GLPK actually seems to return
// GLP_UNDEF. So this is never (?) reached. Return infeasible
// in case GLPK returns a correct status in future versions.
@@ -654,8 +643,8 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
return result_status_;
}
MPSolver::BasisStatus
GLPKInterface::TransformGLPKBasisStatus(int glpk_basis_status) const {
MPSolver::BasisStatus GLPKInterface::TransformGLPKBasisStatus(
int glpk_basis_status) const {
switch (glpk_basis_status) {
case GLP_BS:
return MPSolver::BASIC;
@@ -676,17 +665,12 @@ GLPKInterface::TransformGLPKBasisStatus(int glpk_basis_status) const {
// ------ Query statistics on the solution and the solve ------
int64 GLPKInterface::iterations() const {
if (mip_) {
LOG(WARNING) << "Total number of iterations is not available";
return kUnknownNumberOfIterations;
} else {
if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations;
#if GLP_MINOR_VERSION >= 49
return kUnknownNumberOfIterations;
#else
#if GLP_MINOR_VERSION < 49
if (!mip_ && CheckSolutionIsSynchronized())
return lpx_get_int_parm(lp_, LPX_K_ITCNT);
#endif
}
LOG(WARNING) << "Total number of iterations is not available";
return kUnknownNumberOfIterations;
}
int64 GLPKInterface::nodes() const {
@@ -732,7 +716,6 @@ MPSolver::BasisStatus GLPKInterface::column_status(int variable_index) const {
return TransformGLPKBasisStatus(glpk_basis_status);
}
bool GLPKInterface::CheckSolutionExists() const {
if (result_status_ == MPSolver::ABNORMAL) {
LOG(WARNING) << "Ignoring ABNORMAL status from GLPK: This status may or may"
@@ -759,9 +742,8 @@ bool GLPKInterface::CheckBestObjectiveBoundExists() const {
double GLPKInterface::ComputeExactConditionNumber() const {
if (!IsContinuous()) {
// TODO(user): support MIP.
LOG(DFATAL)
<< "ComputeExactConditionNumber not implemented for"
<< " GLPK_MIXED_INTEGER_PROGRAMMING";
LOG(DFATAL) << "ComputeExactConditionNumber not implemented for"
<< " GLPK_MIXED_INTEGER_PROGRAMMING";
return 0.0;
}
if (!CheckSolutionIsSynchronized()) return 0.0;
@@ -779,18 +761,16 @@ double GLPKInterface::ComputeExactConditionNumber() const {
for (int col = 1; col <= num_cols; ++col) {
column_scaling_factor[col] = glp_get_sjj(lp_, col);
}
return
ComputeInverseScaledBasisL1Norm(
num_rows, num_cols,
row_scaling_factor.get(), column_scaling_factor.get()) *
ComputeScaledBasisL1Norm(
num_rows, num_cols,
row_scaling_factor.get(), column_scaling_factor.get());
return ComputeInverseScaledBasisL1Norm(num_rows, num_cols,
row_scaling_factor.get(),
column_scaling_factor.get()) *
ComputeScaledBasisL1Norm(num_rows, num_cols, row_scaling_factor.get(),
column_scaling_factor.get());
}
double GLPKInterface::ComputeScaledBasisL1Norm(
int num_rows, int num_cols,
double* row_scaling_factor, double* column_scaling_factor) const {
int num_rows, int num_cols, double* row_scaling_factor,
double* column_scaling_factor) const {
double norm = 0.0;
std::unique_ptr<double[]> values(new double[num_rows + 1]);
std::unique_ptr<int[]> indices(new int[num_rows + 1]);
@@ -826,8 +806,8 @@ double GLPKInterface::ComputeScaledBasisL1Norm(
}
double GLPKInterface::ComputeInverseScaledBasisL1Norm(
int num_rows, int num_cols,
double* row_scaling_factor, double* column_scaling_factor) const {
int num_rows, int num_cols, double* row_scaling_factor,
double* column_scaling_factor) const {
// Compute the LU factorization if it doesn't exist yet.
if (!glp_bf_exists(lp_)) {
const int factorize_status = glp_factorize(lp_);
@@ -929,7 +909,7 @@ void GLPKInterface::ConfigureGLPKParameters(const MPSolverParameters& param) {
glp_scale_prob(lp_, GLP_SF_AUTO);
// Use advanced initial basis (options: standard / advanced / Bixby's).
glp_adv_basis(lp_, nullptr);
glp_adv_basis(lp_, 0);
// Set parameters specified by the user.
SetParameters(param);
@@ -955,9 +935,7 @@ void GLPKInterface::SetPrimalTolerance(double value) {
lp_param_.tol_bnd = value;
}
void GLPKInterface::SetDualTolerance(double value) {
lp_param_.tol_dj = value;
}
void GLPKInterface::SetDualTolerance(double value) { lp_param_.tol_dj = value; }
void GLPKInterface::SetPresolveMode(int value) {
switch (value) {
@@ -1004,6 +982,5 @@ MPSolverInterface* BuildGLPKInterface(bool mip, MPSolver* const solver) {
return new GLPKInterface(solver, mip);
}
} // namespace operations_research
#endif // #if defined(USE_GLPK)

View File

@@ -35,7 +35,9 @@
#include "base/hash.h"
#include "base/accurate_sum.h"
#include "linear_solver/linear_solver.pb.h"
#include "linear_solver/linear_solver2.pb.h"
#include "linear_solver/model_exporter.h"
#include "linear_solver/proto_tools.h"
#include "util/fp_utils.h"
@@ -553,6 +555,47 @@ MPSolver::LoadStatus MPSolver::LoadModel(const MPModelProto& input_model) {
return MPSolver::NO_ERROR;
}
MPSolver::LoadStatus MPSolver::LoadModelFromProto(
const new_proto::MPModelProto& input_model) {
for (int i = 0; i < input_model.variable_size(); ++i) {
const new_proto::MPVariableProto& var_proto = input_model.variable(i);
// TODO(user): The variable name var_proto.name() is lost at this point.
// This is because MPSolver has no notion of name, just of ids, and these
// must be unique which is not necessarily the case of names in the new
// proto. MakeNumVar() will just assign an automated variable id when the
// the given name argument is empty.
//
// Note(user): The auto assigned id limits the number of variables to 10^9.
MPVariable* variable = MakeNumVar(
var_proto.lower_bound(), var_proto.upper_bound(), /*name=*/ "");
variable->SetInteger(var_proto.is_integer());
SetObjectiveCoefficient(variable, var_proto.objective_coefficient());
}
for (int i = 0; i < input_model.constraint_size(); ++i) {
const new_proto::MPConstraintProto& ct_proto = input_model.constraint(i);
MPConstraint* const ct = MakeRowConstraint(ct_proto.lower_bound(),
ct_proto.upper_bound(),
ct_proto.name());
for (int j = 0; j < ct_proto.linear_term_size(); ++j) {
const new_proto::MPConstraintProto::UnaryTerm& term_proto =
ct_proto.linear_term(j);
if (term_proto.var_index() >= variables_.size()) {
LOG(ERROR) << "Variable index out of bound in constraint named "
<< ct_proto.name() << ".";
return MPSolver::UNKNOWN_VARIABLE_ID;
}
ct->SetCoefficient(variables_[term_proto.var_index()],
term_proto.coefficient());
}
}
SetOptimizationDirection(input_model.maximize());
if (input_model.has_objective_offset()) {
MutableObjective()->SetOffset(input_model.objective_offset());
}
return MPSolver::NO_ERROR;
}
void MPSolver::FillSolutionResponse(MPSolutionResponse* response) const {
CHECK_NOTNULL(response);
@@ -612,6 +655,40 @@ void MPSolver::FillSolutionResponse(MPSolutionResponse* response) const {
}
}
namespace {
new_proto::MPSolutionResponse::Status ResultStatusToMPSolutionResponse(
MPSolver::ResultStatus status) {
switch (status) {
case MPSolver::OPTIMAL :
return new_proto::MPSolutionResponse::OPTIMAL;
case MPSolver::FEASIBLE :
return new_proto::MPSolutionResponse::FEASIBLE;
case MPSolver::INFEASIBLE :
return new_proto::MPSolutionResponse::INFEASIBLE;
case MPSolver::UNBOUNDED :
return new_proto::MPSolutionResponse::UNBOUNDED;
case MPSolver::ABNORMAL :
return new_proto::MPSolutionResponse::ABNORMAL;
default:
return new_proto::MPSolutionResponse::UNKNOWN;
}
}
} // namespace
void MPSolver::FillSolutionResponseProto(
new_proto::MPSolutionResponse* response) const {
CHECK_NOTNULL(response);
response->Clear();
response->set_status(
ResultStatusToMPSolutionResponse(interface_->result_status_));
if (interface_->result_status_ == MPSolver::OPTIMAL ||
interface_->result_status_ == MPSolver::FEASIBLE) {
response->set_objective_value(objective_value());
for (int i = 0; i < variables_.size(); ++i) {
response->add_variable_value(variables_[i]->solution_value());
}
}
}
// static
void MPSolver::SolveWithProtocolBuffers(const MPModelRequest& model_request,
@@ -624,6 +701,8 @@ 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 = "
<< Error::Code_Name(static_cast<Error::Code>(loadStatus))
<< " (" << loadStatus << ")";
response->set_result_status(MPSolutionResponse::ABNORMAL);
return;
@@ -636,6 +715,33 @@ void MPSolver::SolveWithProtocolBuffers(const MPModelRequest& model_request,
solver.FillSolutionResponse(response);
}
// static
void MPSolver::SolveWithProto(
const new_proto::MPModelRequest& model_request,
new_proto::MPSolutionResponse* response) {
CHECK_NOTNULL(response);
const new_proto::MPModelProto& model = model_request.model();
MPSolver solver(model.name(),
static_cast<MPSolver::OptimizationProblemType>(
model_request.solver_type()));
const MPSolver::LoadStatus loadStatus = solver.LoadModelFromProto(model);
if (loadStatus != MPSolver::NO_ERROR) {
LOG(WARNING) << "Loading model from protocol buffer failed, "
<< "load status = "
<< Error::Code_Name(static_cast<Error::Code>(loadStatus))
<< " (" << loadStatus << ")";
response->set_status(new_proto::MPSolutionResponse::ABNORMAL);
return;
}
if (model_request.has_solver_time_limit_seconds()) {
// static_cast<int64> avoids a warning with -Wreal-conversion. This
// helps catching bugs with unwanted conversions from double to ints.
solver.set_time_limit(
static_cast<int64>(model_request.solver_time_limit_seconds()) * 1000);
}
solver.Solve();
solver.FillSolutionResponseProto(response);
}
namespace {
// Outputs the terms in var_coeff_map to output_term, by sorting the
@@ -673,32 +779,32 @@ void OutputTermsToProto(
void MPSolver::ExportModel(MPModelProto* output_model) const {
DCHECK(output_model != NULL);
if (output_model->variables_size() > 0 ||
output_model->has_maximize() ||
output_model->objective_terms_size() > 0 ||
output_model->constraints_size() > 0 ||
output_model->has_name() ||
output_model->has_objective_offset()) {
LOG(WARNING) << "The model protocol buffer is not empty, "
<< "it will be overwritten.";
output_model->clear_variables();
output_model->clear_maximize();
output_model->clear_objective_terms();
output_model->clear_constraints();
output_model->clear_name();
}
new_proto::MPModelProto new_proto_model;
ExportModelToNewProto(&new_proto_model);
ConvertNewMPModelProtoToOld(new_proto_model, output_model);
}
void MPSolver::ExportModelToNewProto(
new_proto::MPModelProto* output_model) const {
DCHECK(output_model != NULL);
output_model->Clear();
// Name
output_model->set_name(Name());
// Variables
for (int j = 0; j < variables_.size(); ++j) {
const MPVariable* const var = variables_[j];
MPVariableProto* const variable_proto = output_model->add_variables();
DCHECK(!var->name().empty());
variable_proto->set_id(var->name());
variable_proto->set_lb(var->lb());
variable_proto->set_ub(var->ub());
variable_proto->set_integer(var->integer());
new_proto::MPVariableProto* const variable_proto
= output_model->add_variable();
// TODO(user): Add option to avoid filling the var name to avoid overly
// large protocol buffers.
variable_proto->set_name(var->name());
variable_proto->set_lower_bound(var->lb());
variable_proto->set_upper_bound(var->ub());
variable_proto->set_is_integer(var->integer());
if (objective_->GetCoefficient(var) != 0.0) {
variable_proto->set_objective_coefficient(
objective_->GetCoefficient(var));
}
}
// Map the variables to their indices. This is needed to output the
@@ -707,37 +813,56 @@ void MPSolver::ExportModel(MPModelProto* output_model) const {
// 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;
hash_map<const MPVariable*, int> var_to_index;
for (int j = 0; j < variables_.size(); ++j) {
var_name_to_index[variables_[j]] = j;
var_to_index[variables_[j]] = j;
}
// Constraints
for (int i = 0; i < constraints_.size(); ++i) {
MPConstraint* const constraint = constraints_[i];
MPConstraintProto* const constraint_proto = output_model->add_constraints();
// Constraint names need to be non-empty.
DCHECK(!constraint->name().empty());
constraint_proto->set_id(constraint->name());
constraint_proto->set_lb(constraint->lb());
constraint_proto->set_ub(constraint->ub());
OutputTermsToProto(variables_, var_name_to_index, constraint->coefficients_,
constraint_proto->mutable_terms());
new_proto::MPConstraintProto* const constraint_proto
= output_model->add_constraint();
constraint_proto->set_name(constraint->name());
constraint_proto->set_lower_bound(constraint->lb());
constraint_proto->set_upper_bound(constraint->ub());
constraint_proto->set_is_lazy(constraint->is_lazy());
// 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_) {
const MPVariable* const var = entry.first;
const int var_index = FindWithDefault(var_to_index, var, -1);
DCHECK_NE(-1, var_index);
const double coeff = entry.second;
linear_term.push_back(std::pair<int, double>(var_index, coeff));
}
// The cost of std::sort is expected to be low as constraints usually have very
// few terms.
std::sort(linear_term.begin(), linear_term.end());
// Now use linear term.
for (int k = 0; k < linear_term.size(); ++k) {
const std::pair<int, double>& p = linear_term[k];
const int var_index = p.first;
const double coeff = p.second;
new_proto::MPConstraintProto_UnaryTerm* unary_term
= constraint_proto->add_linear_term();
unary_term->set_var_index(var_index);
unary_term->set_coefficient(coeff);
constraint_proto->add_var_index(var_index);
constraint_proto->add_coefficient(coeff);
}
}
// 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());
}
bool MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response) {
bool MPSolver::LoadSolutionFromNewProto(
const new_proto::MPSolutionResponse& response) {
interface_->result_status_ =
static_cast<ResultStatus>(response.result_status());
if (response.result_status() != MPSolutionResponse::OPTIMAL &&
response.result_status() != MPSolutionResponse::FEASIBLE) {
static_cast<ResultStatus>(response.status());
if (response.status() != new_proto::MPSolutionResponse::OPTIMAL &&
response.status() != new_proto::MPSolutionResponse::FEASIBLE) {
LOG(ERROR)
<< "Cannot load a solution unless its status is OPTIMAL or FEASIBLE.";
return false;
@@ -745,55 +870,37 @@ bool MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response) {
// Before touching the variables, verify that the solution looks legit:
// each variable of the MPSolver must have its value listed exactly once, and
// each listed solution should correspond to a known variable.
if (response.solution_values_size() != variables_.size()) {
if (response.variable_value_size() != variables_.size()) {
LOG(ERROR)
<< "Trying to load a solution whose number of variables does not"
<< " correspond to the Solver.";
return false;
}
std::vector<bool> variable_has_solution_value(variables_.size(), false);
double largest_error = 0;
interface_->ExtractModel();
int num_vars_out_of_bounds = 0;
for (int i = 0; i < response.solution_values_size(); ++i) {
const string& var_name = response.solution_values(i).variable_id();
const int var_index =
FindWithDefault(variable_name_to_index_, var_name, -1);
if (var_index < 0) {
LOG(ERROR)
<< "Trying to load a solution with unknown var '" << var_name << "'.";
return false;
}
if (variable_has_solution_value[var_index]) {
LOG(ERROR)
<< "Trying to load a solution value where variable '" << var_name
<< "' has its solution value listed twice.";
return false;
}
variable_has_solution_value[var_index] = true;
const double tolerance = MPSolverParameters::kDefaultPrimalTolerance;
for (int i = 0; i< response.variable_value_size(); ++i) {
// Look further: verify the bounds. Since linear solvers yield (small)
// numerical errors, though, we just log a warning if the variables look
// like they are out of their bounds. The user should inspect the values.
const double var_value = response.solution_values(i).value();
const MPVariable* var = variables_[var_index];
DCHECK_EQ(var->name(), var_name);
if (var_value < var->lb() || var_value > var->ub()) {
const double var_value = response.variable_value(i);
MPVariable* var = variables_[i];
// TODO(user): Use parameter when they become available in this class.
const double lb_error = var->lb() - var_value;
const double ub_error = var_value - var->ub();
if (lb_error > tolerance || ub_error > tolerance) {
++num_vars_out_of_bounds;
largest_error = std::max(largest_error, std::max(lb_error, ub_error));
}
var->set_solution_value(var_value);
}
if (num_vars_out_of_bounds > 0) {
LOG(WARNING)
<< "Loaded a solution whose variables matched the solver's, but "
<< num_vars_out_of_bounds << " out of " << variables_.size()
<< " had values outside their bounds.";
}
// At this point, we know for sure that the solution can be safely loaded.
// We extract the model onto the underlying solver, so that the accessors work
// properly later (as if we had called Solve()).
interface_->ExtractModel();
// Do a second pass, where we actually set the variable values.
for (int i = 0; i < response.solution_values_size(); ++i) {
variables_[FindOrDie(variable_name_to_index_,
response.solution_values(i).variable_id())]
->set_solution_value(response.solution_values(i).value());
<< " exceed one of their bounds by more tahn the primal tolerance: "
<< tolerance;
}
// Set the objective value, if is known.
if (response.has_objective_value()) {
@@ -1579,5 +1686,81 @@ int MPSolverParameters::GetIntegerParam(
}
bool MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response) {
// CHANGES TO THIS METHOD ARE DISCOURAGED.
// This method will be deprecated in 2014.
// Please try using LoadSolutionFromNewProto() instead.
// TODO(user): Deprecate then remove this code.
interface_->result_status_ =
static_cast<ResultStatus>(response.result_status());
if (response.result_status() != MPSolutionResponse::OPTIMAL &&
response.result_status() != MPSolutionResponse::FEASIBLE) {
LOG(ERROR)
<< "Cannot load a solution unless its status is OPTIMAL or FEASIBLE.";
return false;
}
// Before touching the variables, verify that the solution looks legit:
// each variable of the MPSolver must have its value listed exactly once, and
// each listed solution should correspond to a known variable.
if (response.solution_values_size() != variables_.size()) {
LOG(ERROR)
<< "Trying to load a solution whose number of variables does not"
<< " correspond to the Solver.";
return false;
}
std::vector<bool> variable_has_solution_value(variables_.size(), false);
int num_vars_out_of_bounds = 0;
for (int i = 0; i < response.solution_values_size(); ++i) {
const string& var_name = response.solution_values(i).variable_id();
const int var_index =
FindWithDefault(variable_name_to_index_, var_name, -1);
if (var_index < 0) {
LOG(ERROR)
<< "Trying to load a solution with unknown var '" << var_name << "'.";
return false;
}
if (variable_has_solution_value[var_index]) {
LOG(ERROR)
<< "Trying to load a solution value where variable '" << var_name
<< "' has its solution value listed twice.";
return false;
}
variable_has_solution_value[var_index] = true;
// Look further: verify the bounds. Since linear solvers yield (small)
// numerical errors, though, we just log a warning if the variables look
// like they are out of their bounds. The user should inspect the values.
const double var_value = response.solution_values(i).value();
const MPVariable* var = variables_[var_index];
DCHECK_EQ(var->name(), var_name);
if (var_value < var->lb() || var_value > var->ub()) {
++num_vars_out_of_bounds;
}
}
if (num_vars_out_of_bounds > 0) {
LOG(WARNING)
<< "Loaded a solution whose variables matched the solver's, but "
<< num_vars_out_of_bounds << " out of " << variables_.size()
<< " had values outside their bounds.";
}
// At this point, we know for sure that the solution can be safely loaded.
// We extract the model onto the underlying solver, so that the accessors work
// properly later (as if we had called Solve()).
interface_->ExtractModel();
// Do a second pass, where we actually set the variable values.
for (int i = 0; i < response.solution_values_size(); ++i) {
variables_[FindOrDie(variable_name_to_index_,
response.solution_values(i).variable_id())]
->set_solution_value(response.solution_values(i).value());
}
// Set the objective value, if is known.
if (response.has_objective_value()) {
interface_->objective_value_ = response.objective_value();
}
// Mark the status as SOLUTION_SYNCHRONIZED, so that users may inspect the
// solution normally.
interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED;
return true;
}
} // namespace operations_research

View File

@@ -158,6 +158,13 @@ class MPSolverInterface;
class MPSolverParameters;
class MPVariable;
// The new MP protocol buffer format. New clients that want to work with
// protocol buffers should use this.
namespace new_proto {
class MPModelProto;
class MPModelRequest;
class MPSolutionResponse;
} // namespace new_proto
// This mathematical programming (MP) solver class is the main class
// though which users build and solve problems.
@@ -351,12 +358,12 @@ class MPSolver {
};
// Loads model from protocol buffer.
LoadStatus LoadModel(const MPModelProto& input_model);
LoadStatus LoadModelFromProto(const new_proto::MPModelProto& input_model);
// Encodes the current solution in a solution response protocol buffer.
// Only nonzero variable values are stored in order to reduce the
// size of the MPSolutionResponse protocol buffer.
void FillSolutionResponse(MPSolutionResponse* response) const;
void FillSolutionResponseProto(new_proto::MPSolutionResponse* response) const;
// Solves the model encoded by a MPModelRequest protocol buffer and
// fills the solution encoded as a MPSolutionResponse.
@@ -364,27 +371,28 @@ class MPSolver {
// end. If you want to keep the MPSolver alive (for debugging, or for
// incremental solving), you should write another version of this function
// that creates the MPSolver object on the heap and returns it.
static void SolveWithProtocolBuffers(const MPModelRequest& model_request,
MPSolutionResponse* response);
static void SolveWithProto(
const new_proto::MPModelRequest& model_request,
new_proto::MPSolutionResponse* response);
// Exports model to protocol buffer.
void ExportModel(MPModelProto* output_model) const;
// TODO(user): rename to ExportModelToProto when possible.
void ExportModelToNewProto(new_proto::MPModelProto* output_model) const;
// Load a solution encoded in a protocol buffer onto this solver.
// Load a solution encoded in a protocol buffer onto this solver for easy
// access via the MPSolver interface.
//
// IMPORTANT: This may only be used in conjunction with ExportModel(),
// following this example:
// MPSolver my_solver;
// ... add variables and constraints ...
// MPModelProto model_proto;
// my_solver.ExportModel(&model_proto);
// MPSolutionResponse solver_response;
// new_proto::MPModelProto model_proto;
// my_solver.ExportModelToNewProto(&model_proto);
// new_proto::MPSolutionResponse solver_response;
// // This can be replaced by a stubby call to the linear solver server.
// MPSolver::SolveWithProtocolBuffers(model_proto, &solver_response);
// MPSolver::SolveWithProto(model_proto, &solver_response);
// if (solver_response.result_status() == MPSolutionResponse::OPTIMAL) {
// CHECK(my_solver.LoadSolutionFromProto(solver_response));
// CHECK(my_solver.LoadSolutionFromNewProto(solver_response));
// ... inspect the solution using the usual API: solution_value(), etc...
// }
//
@@ -400,12 +408,14 @@ class MPSolver {
// - loading a solution with a status other than OPTIMAL / FEASIBLE.
// Note: the variable and objective values aren't checked. You can use
// VerifySolution() for that.
bool LoadSolutionFromProto(const MPSolutionResponse& response);
// TODO(user): rename to LoadSolutionFromProto when possible.
bool LoadSolutionFromNewProto(
const new_proto::MPSolutionResponse& response);
// ----- Export model to files or strings -----
// Shortcuts to the homonymous MPModelProtoExporter methods, via
// exporting to a MPModelProto with ExportModel() (see above).
// exporting to a MPModelProto with ExportModelToNewProto() (see above).
bool ExportModelAsLpFormat(bool obfuscated, string* model_str);
bool ExportModelAsMpsFormat(
bool fixed_format, bool obfuscated, string* model_str);
@@ -548,6 +558,16 @@ class MPSolver {
bool Maximization() const;
bool Minimization() const;
// DEPRECATED methods. Use the explicitly listed replacement.
LoadStatus LoadModel(const MPModelProto& input_model); // LoadModelFromProto
void FillSolutionResponse( // FillSolutionResponseProto
MPSolutionResponse* response) const;
static void SolveWithProtocolBuffers( // SolveWithProto
const MPModelRequest& model_request, MPSolutionResponse* response);
void ExportModel(MPModelProto* output_model) const; // ExportModelToNewProto
bool LoadSolutionFromProto( // LoadSolutionFromNewProto
const MPSolutionResponse& response);
private:
// Computes the size of the constraint with the largest number of
// coefficients with index in [min_constraint_index,

View File

@@ -87,3 +87,14 @@ message MPSolutionResponse {
repeated MPSolutionValue solution_values = 3;
}
message Error {
enum Code {
NO_ERROR = 0;
INVALID_PROBLEM_TYPE = 1;
DUPLICATE_VARIABLE_ID = 2;
UNKNOWN_VARIABLE_ID = 3;
REQUEST_IS_QOD = 4;
RPC_DEADLINE_TOO_SMALL = 5;
}
}

View File

@@ -20,6 +20,7 @@
%{
#include "linear_solver/linear_solver.h"
#include "linear_solver/linear_solver.pb.h"
#include "linear_solver/linear_solver2.pb.h"
#include "linear_solver/linear_solver_ext.h"
%}
@@ -526,6 +527,9 @@ namespace operations_research {
// The following 3 methods that use protocol buffers as output
// arguments are replaced by methods that return a protocol buffer,
// see code below.
%ignore MPSolver::ExportModelToNewProto;
%ignore MPSolver::FillSolutionResponseProto;
%ignore MPSolver::SolveWithProto;
%ignore MPSolver::ExportModel;
%ignore MPSolver::FillSolutionResponse;
%ignore MPSolver::SolveWithProtocolBuffers;
@@ -635,6 +639,7 @@ namespace operations_research {
// arguments are replaced by methods that return a protocol buffer,
// see code below.
%ignore MPSolver::ExportModel;
%ignore MPSolver::ExportModelToNewProto;
%ignore MPSolver::FillSolutionResponse;
%ignore MPSolver::SolveWithProtocolBuffers;