add overloaded operators in linear_solver C++ api; extend 'natural' api in python
This commit is contained in:
@@ -307,7 +307,10 @@ void BopInterface::ExtractNewVariables() {
|
||||
DCHECK_EQ(new_col, col);
|
||||
set_variable_as_extracted(col.value(), true);
|
||||
linear_program_.SetVariableBounds(col, var->lb(), var->ub());
|
||||
linear_program_.SetVariableIntegrality(col, var->integer());
|
||||
if (var->integer()) {
|
||||
linear_program_.SetVariableType(
|
||||
col, glop::LinearProgram::VariableType::INTEGER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,14 @@ class MPSolutionResponse;
|
||||
%unignore operations_research::MPSolver::NOT_SOLVED;
|
||||
|
||||
// Expose the MPSolver's basic API, with some non-trivial renames.
|
||||
%rename (MakeConstraint) operations_research::MPSolver::MakeRowConstraint;
|
||||
// We intentionally don't expose MakeRowConstraint(LinearExpr), because this
|
||||
// "natural language" API is specific to C++: other languages may add their own
|
||||
// syntactic sugar on top of MPSolver instead of this.
|
||||
%rename (MakeConstraint) operations_research::MPSolver::MakeRowConstraint(double, double);
|
||||
%rename (MakeConstraint) operations_research::MPSolver::MakeRowConstraint();
|
||||
%rename (MakeConstraint) operations_research::MPSolver::MakeRowConstraint(double, double, const std::string&);
|
||||
%rename (MakeConstraint) operations_research::MPSolver::MakeRowConstraint(const std::string&);
|
||||
|
||||
%rename (Objective) operations_research::MPSolver::MutableObjective;
|
||||
|
||||
// Expose the MPSolver's basic API, with trivial renames when needed.
|
||||
|
||||
@@ -542,25 +542,27 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
|
||||
|
||||
// Solve
|
||||
timer.Restart();
|
||||
int solver_status = glp_simplex(lp_, &lp_param_);
|
||||
if (mip_) {
|
||||
// glp_intopt requires to solve the root LP separately.
|
||||
int simplex_status = glp_simplex(lp_, &lp_param_);
|
||||
// If the root LP was solved successfully, solve the MIP.
|
||||
if (simplex_status == 0) {
|
||||
glp_intopt(lp_, &mip_param_);
|
||||
if (solver_status == 0) {
|
||||
solver_status = glp_intopt(lp_, &mip_param_);
|
||||
} else {
|
||||
// Something abnormal occurred during the root LP solve. It is
|
||||
// highly unlikely that an integer feasible solution is
|
||||
// available at this point, so we don't put any effort in trying
|
||||
// to recover it.
|
||||
result_status_ = MPSolver::ABNORMAL;
|
||||
if (solver_status == GLP_ETMLIM) {
|
||||
result_status_ = MPSolver::NOT_SOLVED;
|
||||
}
|
||||
sync_status_ = SOLUTION_SYNCHRONIZED;
|
||||
return result_status_;
|
||||
}
|
||||
} else {
|
||||
glp_simplex(lp_, &lp_param_);
|
||||
}
|
||||
VLOG(1) << StringPrintf("Solved in %.3f seconds.", timer.Get());
|
||||
VLOG(1) << StringPrintf("GLPK Status: %i (time spent: %.3f seconds).",
|
||||
solver_status, timer.Get());
|
||||
|
||||
// Get the results.
|
||||
if (mip_) {
|
||||
@@ -600,7 +602,7 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
|
||||
// Check the status: optimal, infeasible, etc.
|
||||
if (mip_) {
|
||||
int tmp_status = glp_mip_status(lp_);
|
||||
VLOG(1) << "gplk result status: " << tmp_status;
|
||||
VLOG(1) << "GLPK result status: " << tmp_status;
|
||||
if (tmp_status == GLP_OPT) {
|
||||
result_status_ = MPSolver::OPTIMAL;
|
||||
} else if (tmp_status == GLP_FEAS) {
|
||||
@@ -610,6 +612,8 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
|
||||
// GLP_UNDEF. So this is never (?) reached. Return infeasible
|
||||
// in case GLPK returns a correct status in future versions.
|
||||
result_status_ = MPSolver::INFEASIBLE;
|
||||
} else if (solver_status == GLP_ETMLIM) {
|
||||
result_status_ = MPSolver::NOT_SOLVED;
|
||||
} else {
|
||||
result_status_ = MPSolver::ABNORMAL;
|
||||
// GLPK does not have a status code for unbounded MIP models, so
|
||||
@@ -617,7 +621,7 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
|
||||
}
|
||||
} else {
|
||||
int tmp_status = glp_get_status(lp_);
|
||||
VLOG(1) << "gplk result status: " << tmp_status;
|
||||
VLOG(1) << "GLPK result status: " << tmp_status;
|
||||
if (tmp_status == GLP_OPT) {
|
||||
result_status_ = MPSolver::OPTIMAL;
|
||||
} else if (tmp_status == GLP_FEAS) {
|
||||
@@ -632,6 +636,8 @@ MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) {
|
||||
// GLP_UNDEF. So this is never (?) reached. Return unbounded
|
||||
// in case GLPK returns a correct status in future versions.
|
||||
result_status_ = MPSolver::UNBOUNDED;
|
||||
} else if (solver_status == GLP_ETMLIM) {
|
||||
result_status_ = MPSolver::NOT_SOLVED;
|
||||
} else {
|
||||
result_status_ = MPSolver::ABNORMAL;
|
||||
}
|
||||
|
||||
@@ -700,9 +700,9 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
|
||||
default: {
|
||||
if (solution_count > 0) {
|
||||
result_status_ = MPSolver::FEASIBLE;
|
||||
} else if (optimization_status == GRB_TIME_LIMIT) {
|
||||
result_status_ = MPSolver::NOT_SOLVED;
|
||||
} else {
|
||||
// TODO(user,user): We could introduce additional values for the
|
||||
// status: for example, stopped because of time limit.
|
||||
result_status_ = MPSolver::ABNORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -152,7 +152,13 @@ import java.lang.reflect.*;
|
||||
|
||||
// Expose the MPSolver's basic API, with some non-trivial renames.
|
||||
%rename (objective) operations_research::MPSolver::MutableObjective;
|
||||
%rename (makeConstraint) operations_research::MPSolver::MakeRowConstraint;
|
||||
// We intentionally don't expose MakeRowConstraint(LinearExpr), because this
|
||||
// "natural language" API is specific to C++: other languages may add their own
|
||||
// syntactic sugar on top of MPSolver instead of this.
|
||||
%rename (makeConstraint) operations_research::MPSolver::MakeRowConstraint(double, double);
|
||||
%rename (makeConstraint) operations_research::MPSolver::MakeRowConstraint();
|
||||
%rename (makeConstraint) operations_research::MPSolver::MakeRowConstraint(double, double, const std::string&);
|
||||
%rename (makeConstraint) operations_research::MPSolver::MakeRowConstraint(const std::string&);
|
||||
|
||||
// Expose the MPSolver's basic API, with trivial renames.
|
||||
%rename (makeBoolVar) operations_research::MPSolver::MakeBoolVar; // no test
|
||||
|
||||
123
src/linear_solver/linear_expr.cc
Normal file
123
src/linear_solver/linear_expr.cc
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2010-2014 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
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "linear_solver/linear_expr.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
namespace {
|
||||
|
||||
const MPVariable* dense_hash_map_empty_key() {
|
||||
static const int* empty_key = new int;
|
||||
return reinterpret_cast<const MPVariable*>(empty_key);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LinearExpr::LinearExpr(double constant) : offset_(constant), terms_() {
|
||||
}
|
||||
LinearExpr::LinearExpr() : LinearExpr(0.0) {}
|
||||
|
||||
LinearExpr::LinearExpr(const MPVariable* var) : LinearExpr(0.0) {
|
||||
terms_[var] = 1.0;
|
||||
}
|
||||
|
||||
LinearExpr& LinearExpr::operator+=(const LinearExpr& rhs) {
|
||||
for (const auto& kv : rhs.terms_) {
|
||||
terms_[kv.first] += kv.second;
|
||||
}
|
||||
offset_ += rhs.offset_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
LinearExpr& LinearExpr::operator-=(const LinearExpr& rhs) {
|
||||
for (const auto& kv : rhs.terms_) {
|
||||
terms_[kv.first] -= kv.second;
|
||||
}
|
||||
offset_ -= rhs.offset_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
LinearExpr& LinearExpr::operator*=(double rhs) {
|
||||
if (rhs == 0) {
|
||||
terms_.clear();
|
||||
offset_ = 0;
|
||||
} else if (rhs != 1) {
|
||||
for (auto& kv : terms_) {
|
||||
kv.second *= rhs;
|
||||
}
|
||||
offset_ *= rhs;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
LinearExpr& LinearExpr::operator/=(double rhs) {
|
||||
DCHECK_NE(rhs, 0);
|
||||
return (*this) *= 1 / rhs;
|
||||
}
|
||||
|
||||
LinearExpr LinearExpr::operator-() const { return (*this) * -1; }
|
||||
|
||||
// static
|
||||
LinearExpr LinearExpr::NotVar(LinearExpr var) {
|
||||
var *= -1;
|
||||
var += 1;
|
||||
return var;
|
||||
}
|
||||
|
||||
LinearExpr operator+(LinearExpr lhs, const LinearExpr& rhs) {
|
||||
lhs += rhs;
|
||||
return lhs;
|
||||
}
|
||||
LinearExpr operator-(LinearExpr lhs, const LinearExpr& rhs) {
|
||||
lhs -= rhs;
|
||||
return lhs;
|
||||
}
|
||||
LinearExpr operator*(LinearExpr lhs, double rhs) {
|
||||
lhs *= rhs;
|
||||
return lhs;
|
||||
}
|
||||
LinearExpr operator/(LinearExpr lhs, double rhs) {
|
||||
lhs /= rhs;
|
||||
return lhs;
|
||||
}
|
||||
LinearExpr operator*(double lhs, LinearExpr rhs) {
|
||||
rhs *= lhs;
|
||||
return rhs;
|
||||
}
|
||||
|
||||
LinearRange::LinearRange(double lower_bound, const LinearExpr& linear_expr,
|
||||
double upper_bound)
|
||||
: lower_bound_(lower_bound),
|
||||
linear_expr_(linear_expr),
|
||||
upper_bound_(upper_bound) {
|
||||
lower_bound_ -= linear_expr_.offset();
|
||||
upper_bound_ -= linear_expr_.offset();
|
||||
linear_expr_ -= linear_expr_.offset();
|
||||
}
|
||||
|
||||
LinearRange operator<=(const LinearExpr& lhs, const LinearExpr& rhs) {
|
||||
return LinearRange(-std::numeric_limits<double>::infinity(), lhs - rhs, 0);
|
||||
}
|
||||
LinearRange operator==(const LinearExpr& lhs, const LinearExpr& rhs) {
|
||||
return LinearRange(0, lhs - rhs, 0);
|
||||
}
|
||||
LinearRange operator>=(const LinearExpr& lhs, const LinearExpr& rhs) {
|
||||
return LinearRange(0, lhs - rhs, std::numeric_limits<double>::infinity());
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
178
src/linear_solver/linear_expr.h
Normal file
178
src/linear_solver/linear_expr.h
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright 2010-2014 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
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_LINEAR_SOLVER_LINEAR_EXPR_H_
|
||||
#define OR_TOOLS_LINEAR_SOLVER_LINEAR_EXPR_H_
|
||||
|
||||
// This file allows you to write natural code (like a mathematical equation) to
|
||||
// model optimization problems with MPSolver. It is syntatic sugar on top of
|
||||
// the MPSolver API, it provides no additional functionality. Use of these APIs
|
||||
// makes it much easier to write code that is both simple and big-O optimal for
|
||||
// creating your model, at the cost of some additional constant factor
|
||||
// overhead. If model creation is a bottleneck in your problem, consider using
|
||||
// the MPSolver API directly instead.
|
||||
//
|
||||
// This file contains two classes:
|
||||
// 1. LinearExpr: models offset + sum_{i in S} a_i*x_i for decision var x_i,
|
||||
// 2. LinearRange: models lb <= sum_{i in S} a_i*x_i <= ub,
|
||||
// and it provides various operator overloads to build up "LinearExpr"s and
|
||||
// then convert them to "LinearRange"s.
|
||||
//
|
||||
// Recommended use (avoids dangerous code):
|
||||
//
|
||||
// MPSolver solver = ...;
|
||||
// const LinearExpr x = solver.MakeVar(...); // Note: implicit conversion
|
||||
// const LinearExpr y = solver.MakeVar(...);
|
||||
// const LinearExpr z = solver.MakeVar(...);
|
||||
// const LinearExpr e1 = x + y;
|
||||
// const LinearExpr e2 = (e1 + 7.0 + z)/3.0;
|
||||
// const LinearRange r = e1 <= e2;
|
||||
// solver.AddRowConstraint(r);
|
||||
//
|
||||
// WARNING, AVOID THIS TRAP:
|
||||
//
|
||||
// MPSolver solver = ...;
|
||||
// MPVariable* x = solver.MakeVar(...);
|
||||
// LinearExpr y = x + 5;
|
||||
//
|
||||
// In evaluating "x+5" above, x is NOT converted to a LinearExpr before the
|
||||
// addition, but rather is treated as a pointer, so x+5 gives a new pointer to
|
||||
// garbage memory.
|
||||
//
|
||||
// For this reason, when using LinearExpr, it is best practice to:
|
||||
// 1. use double literals instead of ints (e.g. "x + 5.0", not "x + 5"),
|
||||
// 2. Immediately convert all MPVariable* to LinearExpr on creation, and only
|
||||
// hold references to the "LinearExpr"s.
|
||||
//
|
||||
// Likewise, the following code is NOT recommended:
|
||||
// MPSolver solver = ...;
|
||||
// MPVariable* x = solver.MakeVar(...);
|
||||
// MPVariable* y = solver.MakeVar(...);
|
||||
// LinearExpr e1 = LinearExpr(x) + y + 5;
|
||||
//
|
||||
// While it is correct, it violates the natural assumption that the + operator
|
||||
// is associative. Thus you are setting a trap for future modifications of the
|
||||
// code, as any of the following changes would lead to the above failure mode:
|
||||
// * LinearExpr e1 = LinearExpr(x) + (y + 5);
|
||||
// * LinearExpr e1 = y + 5 + LinearExpr(x);
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// NOTE(user): forward declaration is necessary due to cyclic dependencies,
|
||||
// MPVariable is defined in linear_solver.h, which depends on LinearExpr.
|
||||
class MPVariable;
|
||||
|
||||
// LinearExpr models a quantity that is linear in the decision variables
|
||||
// (MPVariable) of an optimization problem, i.e.
|
||||
//
|
||||
// offset + sum_{i in S} a_i*x_i,
|
||||
//
|
||||
// where the a_i and offset are constants and the x_i are MPVariables. You can
|
||||
// use a LinearExpr "linear_expr" with an MPSolver "solver" to:
|
||||
// * Set as the objective of your optimization problem, e.g.
|
||||
//
|
||||
// solver.MutableObjective()->MaximizeLinearExpr(linear_expr);
|
||||
//
|
||||
// * Create a constraint in your optimization, e.g.
|
||||
//
|
||||
// solver.AddRowConstraint(linear_expr1 <= linear_expr2);
|
||||
//
|
||||
// * Get the value of the quantity after solving, e.g.
|
||||
//
|
||||
// solver.Solve();
|
||||
// solver.SolutionValue(linear_expr);
|
||||
//
|
||||
// LinearExpr is allowed to delete variables with coefficient zero from the map,
|
||||
// but is not obligated to do so.
|
||||
class LinearExpr {
|
||||
public:
|
||||
LinearExpr();
|
||||
// Possible implicit conversions are intentional.
|
||||
LinearExpr(double constant); // NOLINT
|
||||
// Possible implicit conversions are intentional.
|
||||
// Warning: var is not owned.
|
||||
LinearExpr(const MPVariable* var); // NOLINT
|
||||
|
||||
// Returns 1-var.
|
||||
// NOTE(user): if var is binary variable, this corresponds to the logical
|
||||
// negation of var.
|
||||
// Passing by value is intentional, see the discussion on binary ops.
|
||||
static LinearExpr NotVar(LinearExpr var);
|
||||
|
||||
LinearExpr& operator+=(const LinearExpr& rhs);
|
||||
LinearExpr& operator-=(const LinearExpr& rhs);
|
||||
LinearExpr& operator*=(double rhs);
|
||||
LinearExpr& operator/=(double rhs);
|
||||
LinearExpr operator-() const;
|
||||
|
||||
double offset() const { return offset_; }
|
||||
const std::unordered_map<const MPVariable*, double>& terms() const {
|
||||
return terms_;
|
||||
}
|
||||
|
||||
private:
|
||||
double offset_;
|
||||
std::unordered_map<const MPVariable*, double> terms_;
|
||||
};
|
||||
|
||||
// NOTE(user): in the ops below, the non-"const LinearExpr&" are intentional.
|
||||
// We need to create a new LinearExpr for the result, so we lose nothing by
|
||||
// passing one argument by value, mutating it, and then returning it. In
|
||||
// particular, this allows (with move semantics and RVO) an optimized
|
||||
// evaluation of expressions such as
|
||||
// a + b + c + d
|
||||
// (see http://en.cppreference.com/w/cpp/language/operators).
|
||||
LinearExpr operator+(LinearExpr lhs, const LinearExpr& rhs);
|
||||
LinearExpr operator-(LinearExpr lhs, const LinearExpr& rhs);
|
||||
LinearExpr operator*(LinearExpr lhs, double rhs);
|
||||
LinearExpr operator/(LinearExpr lhs, double rhs);
|
||||
LinearExpr operator*(double lhs, LinearExpr rhs);
|
||||
|
||||
// An expression of the form:
|
||||
// lower_bound <= sum_{i in S} a_i*x_i <= upper_bound.
|
||||
// The sum is represented as a LinearExpr with offset 0.
|
||||
//
|
||||
// Must be added to model with
|
||||
// MPSolver::AddRowConstraint(const LinearRange& range[, const std::string& name]);
|
||||
class LinearRange {
|
||||
public:
|
||||
LinearRange();
|
||||
// The bounds of the linear range are updated so that they include the offset
|
||||
// from "linear_expr", i.e., we form the range:
|
||||
// lower_bound - offset <= linear_expr - offset <= upper_bound - offset.
|
||||
LinearRange(double lower_bound, const LinearExpr& linear_expr,
|
||||
double upper_bound);
|
||||
|
||||
double lower_bound() const { return lower_bound_; }
|
||||
const LinearExpr& linear_expr() const { return linear_expr_; }
|
||||
double upper_bound() const { return upper_bound_; }
|
||||
|
||||
private:
|
||||
double lower_bound_;
|
||||
// invariant: linear_expr_.offset() == 0.
|
||||
LinearExpr linear_expr_;
|
||||
double upper_bound_;
|
||||
};
|
||||
|
||||
LinearRange operator<=(const LinearExpr& lhs, const LinearExpr& rhs);
|
||||
LinearRange operator==(const LinearExpr& lhs, const LinearExpr& rhs);
|
||||
LinearRange operator>=(const LinearExpr& lhs, const LinearExpr& rhs);
|
||||
|
||||
// TODO(user, ondrasej): explore defining more overloads to support:
|
||||
// solver.AddRowConstraint(0.0 <= x + y + z <= 1.0);
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_LINEAR_SOLVER_LINEAR_EXPR_H_
|
||||
@@ -196,6 +196,36 @@ void MPObjective::SetOffset(double value) {
|
||||
interface_->SetObjectiveOffset(offset_);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void CheckLinearExpr(const MPSolver& solver, const LinearExpr& linear_expr) {
|
||||
for (auto var_value_pair : linear_expr.terms()) {
|
||||
CHECK(solver.OwnsVariable(var_value_pair.first))
|
||||
<< "Bad MPVariable* in LinearExpr, did you try adding an integer to an "
|
||||
"MPVariable* directly?";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void MPObjective::OptimizeLinearExpr(const LinearExpr& linear_expr,
|
||||
bool is_maximization) {
|
||||
CheckLinearExpr(*interface_->solver_, linear_expr);
|
||||
interface_->ClearObjective();
|
||||
coefficients_.clear();
|
||||
offset_ = linear_expr.offset();
|
||||
for (const auto& kv : linear_expr.terms()) {
|
||||
SetCoefficient(kv.first, kv.second);
|
||||
}
|
||||
SetOptimizationDirection(is_maximization);
|
||||
}
|
||||
|
||||
void MPObjective::AddLinearExpr(const LinearExpr& linear_expr) {
|
||||
CheckLinearExpr(*interface_->solver_, linear_expr);
|
||||
offset_ += linear_expr.offset();
|
||||
for (const auto& kv : linear_expr.terms()) {
|
||||
SetCoefficient(kv.first, GetCoefficient(kv.first) + kv.second);
|
||||
}
|
||||
}
|
||||
|
||||
void MPObjective::Clear() {
|
||||
interface_->ClearObjective();
|
||||
coefficients_.clear();
|
||||
@@ -841,6 +871,21 @@ MPConstraint* MPSolver::MakeRowConstraint(const std::string& name) {
|
||||
return MakeRowConstraint(-infinity(), infinity(), name);
|
||||
}
|
||||
|
||||
MPConstraint* MPSolver::MakeRowConstraint(const LinearRange& range) {
|
||||
return MakeRowConstraint(range, "");
|
||||
}
|
||||
|
||||
MPConstraint* MPSolver::MakeRowConstraint(const LinearRange& range,
|
||||
const std::string& name) {
|
||||
CheckLinearExpr(*this, range.linear_expr());
|
||||
MPConstraint* constraint =
|
||||
MakeRowConstraint(range.lower_bound(), range.upper_bound(), name);
|
||||
for (const auto& kv : range.linear_expr().terms()) {
|
||||
constraint->SetCoefficient(kv.first, kv.second);
|
||||
}
|
||||
return constraint;
|
||||
}
|
||||
|
||||
int MPSolver::ComputeMaxConstraintSize(int min_constraint_index,
|
||||
int max_constraint_index) const {
|
||||
int max_constraint_size = 0;
|
||||
@@ -902,6 +947,14 @@ MPSolver::ResultStatus MPSolver::Solve(const MPSolverParameters& param) {
|
||||
return status;
|
||||
}
|
||||
|
||||
double MPSolver::SolutionValue(const LinearExpr& linear_expr) const {
|
||||
double ans = linear_expr.offset();
|
||||
for (const auto& kv : linear_expr.terms()) {
|
||||
ans += (kv.second * kv.first->solution_value());
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
void MPSolver::Write(const std::string& file_name) { interface_->Write(file_name); }
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
//
|
||||
// A C++ wrapper that provides a simple and unified interface to
|
||||
// several linear programming and mixed integer programming solvers:
|
||||
// GLPK, CLP, CBC and SCIP. The wrapper can also be used in Java and
|
||||
// Python via SWIG.
|
||||
// GLOP, GLPK, CLP, CBC, and SCIP. The wrapper can also be used in Java, C#,
|
||||
// and Python via SWIG.
|
||||
//
|
||||
//
|
||||
// -----------------------------------
|
||||
@@ -125,7 +125,7 @@
|
||||
// MPSolver stores a representation of the model (variables,
|
||||
// constraints and objective) in its own data structures and a
|
||||
// pointer to a MPSolverInterface that wraps the underlying solver
|
||||
// (CBC, CLP, GLPK or SCIP) that does the actual work. The
|
||||
// (GLOP, CBC, CLP, GLPK, or SCIP) that does the actual work. The
|
||||
// underlying solver also keeps a representation of the model in its
|
||||
// own data structures. The model representations in MPSolver and in
|
||||
// the underlying solver are kept in sync by the 'extraction'
|
||||
@@ -150,6 +150,7 @@
|
||||
#include "base/logging.h"
|
||||
#include "base/timer.h"
|
||||
#include "glop/parameters.pb.h"
|
||||
#include "linear_solver/linear_expr.h"
|
||||
#include "linear_solver/linear_solver.pb.h"
|
||||
|
||||
|
||||
@@ -166,7 +167,7 @@ class MPVariable;
|
||||
class MPSolver {
|
||||
public:
|
||||
// The type of problems (LP or MIP) that will be solved and the
|
||||
// underlying solver (GLPK, CLP, CBC or SCIP) that will solve them.
|
||||
// underlying solver (GLOP, GLPK, CLP, CBC or SCIP) that will solve them.
|
||||
// This must remain consistent with MPModelRequest::OptimizationProblemType
|
||||
// (take particular care of the open-source version).
|
||||
enum OptimizationProblemType {
|
||||
@@ -294,6 +295,12 @@ class MPSolver {
|
||||
// Creates a named constraint with -infinity and +infinity bounds.
|
||||
MPConstraint* MakeRowConstraint(const std::string& name);
|
||||
|
||||
// Creates a constraint owned by MPSolver enforcing:
|
||||
// range.lower_bound() <= range.linear_expr() <= range.upper_bound()
|
||||
MPConstraint* MakeRowConstraint(const LinearRange& range);
|
||||
// As above, but also names the constraint.
|
||||
MPConstraint* MakeRowConstraint(const LinearRange& range, const std::string& name);
|
||||
|
||||
// ----- Objective -----
|
||||
// Note that the objective is owned by the solver, and is initialized to
|
||||
// its default value (see the MPObjective class below) at construction.
|
||||
@@ -324,6 +331,10 @@ class MPSolver {
|
||||
// Solves the problem using the specified parameter values.
|
||||
ResultStatus Solve(const MPSolverParameters& param);
|
||||
|
||||
// Call only after calling MPSolver::Solve. Evaluates "linear_expr" for the
|
||||
// variable values at the solution found by solving.
|
||||
double SolutionValue(const LinearExpr& linear_expr) const;
|
||||
|
||||
// Writes the model using the solver internal write function. Currently only
|
||||
// available for Gurobi.
|
||||
void Write(const std::string& file_name);
|
||||
@@ -668,6 +679,19 @@ class MPObjective {
|
||||
// TODO(user): remove this.
|
||||
void AddOffset(double value) { SetOffset(offset() + value); }
|
||||
|
||||
// Resets the current objective to take the value of linear_expr, and sets
|
||||
// the objective direction to maximize if "is_maximize", otherwise minimizes.
|
||||
void OptimizeLinearExpr(const LinearExpr& linear_expr, bool is_maximize);
|
||||
void MaximizeLinearExpr(const LinearExpr& linear_expr) {
|
||||
OptimizeLinearExpr(linear_expr, true);
|
||||
}
|
||||
void MinimizeLinearExpr(const LinearExpr& linear_expr) {
|
||||
OptimizeLinearExpr(linear_expr, false);
|
||||
}
|
||||
|
||||
// Adds linear_expr to the current objective, does not change the direction.
|
||||
void AddLinearExpr(const LinearExpr& linear_expr);
|
||||
|
||||
// Sets the optimization direction (maximize: true or minimize: false).
|
||||
void SetOptimizationDirection(bool maximize);
|
||||
// Sets the optimization direction to minimize.
|
||||
@@ -1079,9 +1103,10 @@ class MPSolverParameters {
|
||||
};
|
||||
|
||||
// This class wraps the actual mathematical programming solvers. Each
|
||||
// solver (CLP, CBC, GLPK, SCIP) has its own interface class that
|
||||
// solver (GLOP, CLP, CBC, GLPK, SCIP) has its own interface class that
|
||||
// derives from this abstract class. This class is never directly
|
||||
// accessed by the user.
|
||||
// @see glop_interface.cc
|
||||
// @see cbc_interface.cc
|
||||
// @see clp_interface.cc
|
||||
// @see glpk_interface.cc
|
||||
|
||||
@@ -211,7 +211,13 @@ from ortools.linear_solver.linear_solver_natural_api import VariableExpr
|
||||
%rename (BoolVar) operations_research::MPSolver::MakeBoolVar; // No unit test
|
||||
%rename (IntVar) operations_research::MPSolver::MakeIntVar;
|
||||
%rename (NumVar) operations_research::MPSolver::MakeNumVar;
|
||||
%rename (Constraint) operations_research::MPSolver::MakeRowConstraint;
|
||||
// We intentionally don't expose MakeRowConstraint(LinearExpr), because this
|
||||
// "natural language" API is specific to C++: other languages may add their own
|
||||
// syntactic sugar on top of MPSolver instead of this.
|
||||
%rename (Constraint) operations_research::MPSolver::MakeRowConstraint(double, double);
|
||||
%rename (Constraint) operations_research::MPSolver::MakeRowConstraint();
|
||||
%rename (Constraint) operations_research::MPSolver::MakeRowConstraint(double, double, const std::string&);
|
||||
%rename (Constraint) operations_research::MPSolver::MakeRowConstraint(const std::string&);
|
||||
%unignore operations_research::MPSolver::~MPSolver;
|
||||
%unignore operations_research::MPSolver::Solve;
|
||||
%unignore operations_research::MPSolver::VerifySolution;
|
||||
@@ -315,6 +321,6 @@ from ortools.linear_solver.linear_solver_natural_api import VariableExpr
|
||||
def setup_variable_operator(opname):
|
||||
setattr(Variable, opname,
|
||||
lambda self, *args: getattr(VariableExpr(self), opname)(*args))
|
||||
for opname in LinearExpr.SUPPORTED_OPERATOR_METHODS:
|
||||
for opname in LinearExpr.OVERRIDDEN_OPERATOR_METHODS:
|
||||
setup_variable_operator(opname)
|
||||
} // %pythoncode
|
||||
|
||||
@@ -593,9 +593,9 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
|
||||
default:
|
||||
if (solution != nullptr) {
|
||||
result_status_ = MPSolver::FEASIBLE;
|
||||
} else if (scip_status == SCIP_STATUS_TIMELIMIT) {
|
||||
result_status_ = MPSolver::NOT_SOLVED;
|
||||
} else {
|
||||
// TODO(user): We could introduce additional values for the
|
||||
// status: for example, stopped because of time limit.
|
||||
result_status_ = MPSolver::ABNORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user