add overloaded operators in linear_solver C++ api; extend 'natural' api in python

This commit is contained in:
Laurent Perron
2017-03-28 16:09:36 +02:00
parent ce026b5c2d
commit a7039abdab
11 changed files with 429 additions and 22 deletions

View File

@@ -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);
}
}
}

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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

View 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

View 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_

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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;