polish ModelBuilder python new code
This commit is contained in:
@@ -949,7 +949,8 @@ class Model:
|
||||
self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset)
|
||||
self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset)
|
||||
self.__helper.add_terms_to_constraint(
|
||||
ct.index, flat_expr.vars, flat_expr.coeffs)
|
||||
ct.index, flat_expr.vars, flat_expr.coeffs
|
||||
)
|
||||
else:
|
||||
raise TypeError(
|
||||
"Not supported:"
|
||||
@@ -988,7 +989,7 @@ class Model:
|
||||
you can check the if a constraint is always false (lb=inf, ub=-inf) by
|
||||
calling EnforcedLinearConstraint.is_always_false()
|
||||
"""
|
||||
if isinstance(ct, mbh.BoundedLinearExpression): # IMPLEMENTME
|
||||
if isinstance(ct, mbh.BoundedLinearExpression):
|
||||
return _add_enforced_linear_constraint_to_helper(
|
||||
ct, self.__helper, var, value, name
|
||||
)
|
||||
|
||||
@@ -52,13 +52,15 @@ using ::operations_research::MPSolutionResponse;
|
||||
using ::operations_research::MPVariableProto;
|
||||
using ::operations_research::mb::AffineExpr;
|
||||
using ::operations_research::mb::BoundedLinearExpression;
|
||||
using ::operations_research::mb::ExprOrValue;
|
||||
using ::operations_research::mb::FixedValue;
|
||||
using ::operations_research::mb::FlatExpression;
|
||||
using ::operations_research::mb::LinearExpr;
|
||||
using ::operations_research::mb::ModelBuilderHelper;
|
||||
using ::operations_research::mb::ModelSolverHelper;
|
||||
using ::operations_research::mb::SolveStatus;
|
||||
using ::operations_research::mb::SumArray;
|
||||
using ::operations_research::mb::Variable;
|
||||
using ::operations_research::mb::WeightedSumArray;
|
||||
|
||||
namespace py = pybind11;
|
||||
using ::py::arg;
|
||||
@@ -166,26 +168,6 @@ std::vector<std::pair<int, double>> SortedGroupedTerms(
|
||||
return terms;
|
||||
}
|
||||
|
||||
LinearExpr* SafeWeightedSum(const std::vector<LinearExpr*>& exprs,
|
||||
const std::vector<double>& coeffs,
|
||||
double constant = 0.0) {
|
||||
if (exprs.size() != coeffs.size()) {
|
||||
ThrowError(PyExc_ValueError,
|
||||
"The number of expressions and coefficients must match.");
|
||||
}
|
||||
return LinearExpr::WeightedSum(exprs, coeffs, constant);
|
||||
}
|
||||
|
||||
LinearExpr* SafeMixedWeightedSum(const std::vector<ExprOrValue>& exprs,
|
||||
const std::vector<double>& coeffs,
|
||||
double constant = 0.0) {
|
||||
if (exprs.size() != coeffs.size()) {
|
||||
ThrowError(PyExc_ValueError,
|
||||
"The number of expressions and coefficients must match.");
|
||||
}
|
||||
return LinearExpr::MixedWeightedSum(exprs, coeffs, constant);
|
||||
}
|
||||
|
||||
const char* kLinearExprClassDoc = R"doc(
|
||||
Holds an linear expression.
|
||||
|
||||
@@ -229,41 +211,126 @@ const char* kVarClassDoc = R"doc(A variable (continuous or integral).
|
||||
model is feasible, or optimal if you provided an objective function.
|
||||
)doc";
|
||||
|
||||
void ProcessExprArg(const py::handle& arg, LinearExpr*& expr,
|
||||
double& float_value) {
|
||||
if (py::isinstance<LinearExpr>(arg)) {
|
||||
expr = arg.cast<LinearExpr*>();
|
||||
} else {
|
||||
float_value = arg.cast<double>();
|
||||
}
|
||||
}
|
||||
|
||||
LinearExpr* SumArguments(py::args args, const py::kwargs& kwargs) {
|
||||
std::vector<LinearExpr*> linear_exprs;
|
||||
double float_offset = 0.0;
|
||||
|
||||
const auto process_arg = [&](const py::handle& arg) -> void {
|
||||
if (py::isinstance<LinearExpr>(arg)) {
|
||||
linear_exprs.push_back(arg.cast<LinearExpr*>());
|
||||
} else {
|
||||
float_offset += arg.cast<double>();
|
||||
}
|
||||
};
|
||||
|
||||
if (args.size() == 0) {
|
||||
return new FixedValue(0.0);
|
||||
} else if (args.size() == 1 && py::isinstance<py::sequence>(args[0])) {
|
||||
// Normal list or tuple argument.
|
||||
py::sequence elements = args[0].cast<py::sequence>();
|
||||
linear_exprs.reserve(elements.size());
|
||||
for (const py::handle& arg : elements) {
|
||||
process_arg(arg);
|
||||
}
|
||||
} else { // Direct sum(x, y, 3, ..) without [].
|
||||
linear_exprs.reserve(args.size());
|
||||
for (const py::handle arg : args) {
|
||||
process_arg(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (kwargs) {
|
||||
for (const auto arg : kwargs) {
|
||||
const std::string arg_name = std::string(py::str(arg.first));
|
||||
if (arg_name == "constant") {
|
||||
float_offset += arg.second.cast<double>();
|
||||
} else {
|
||||
ThrowError(PyExc_ValueError,
|
||||
absl::StrCat("Unknown keyword argument: ", arg_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (linear_exprs.empty()) {
|
||||
return new FixedValue(float_offset);
|
||||
} else if (linear_exprs.size() == 1) {
|
||||
if (float_offset == 0.0) {
|
||||
return linear_exprs[0];
|
||||
} else {
|
||||
return new AffineExpr(linear_exprs[0], 1.0, float_offset);
|
||||
}
|
||||
} else {
|
||||
return new SumArray(linear_exprs, float_offset);
|
||||
}
|
||||
}
|
||||
|
||||
LinearExpr* WeightedSumArguments(py::sequence expressions,
|
||||
const std::vector<double>& coefficients,
|
||||
double offset = 0.0) {
|
||||
if (expressions.size() != coefficients.size()) {
|
||||
ThrowError(PyExc_ValueError,
|
||||
absl::StrCat("LinearExpr::weighted_sum() requires the same "
|
||||
"number of arguments and coefficients: ",
|
||||
expressions.size(), " != ", coefficients.size()));
|
||||
}
|
||||
|
||||
std::vector<LinearExpr*> linear_exprs;
|
||||
std::vector<double> coeffs;
|
||||
linear_exprs.reserve(expressions.size());
|
||||
coeffs.reserve(expressions.size());
|
||||
|
||||
for (int i = 0; i < expressions.size(); ++i) {
|
||||
py::handle arg = expressions[i];
|
||||
LinearExpr* expr = nullptr;
|
||||
double value = 0.0;
|
||||
ProcessExprArg(arg, expr, value);
|
||||
if (expr != nullptr && coefficients[i] != 0.0) {
|
||||
linear_exprs.push_back(expr);
|
||||
coeffs.push_back(coefficients[i]);
|
||||
continue;
|
||||
} else if (value != 0.0) {
|
||||
offset += coefficients[i] * value;
|
||||
}
|
||||
}
|
||||
|
||||
if (linear_exprs.empty()) {
|
||||
return new FixedValue(offset);
|
||||
} else if (linear_exprs.size() == 1) {
|
||||
if (offset == 0.0 && coeffs[0] == 1.0) {
|
||||
return linear_exprs[0];
|
||||
} else {
|
||||
return new AffineExpr(linear_exprs[0], coeffs[0], offset);
|
||||
}
|
||||
} else {
|
||||
return new WeightedSumArray(linear_exprs, coeffs, offset);
|
||||
}
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(model_builder_helper, m) {
|
||||
pybind11_protobuf::ImportNativeProtoCasters();
|
||||
|
||||
py::class_<ExprOrValue>(m, "ExprOrValue")
|
||||
.def(py::init<double>())
|
||||
.def(py::init<int64_t>())
|
||||
.def(py::init<LinearExpr*>())
|
||||
.def_readonly("double_value", &ExprOrValue::value)
|
||||
.def_readonly("expr", &ExprOrValue::expr);
|
||||
|
||||
py::implicitly_convertible<double, ExprOrValue>();
|
||||
py::implicitly_convertible<int, ExprOrValue>();
|
||||
py::implicitly_convertible<LinearExpr*, ExprOrValue>();
|
||||
|
||||
py::class_<LinearExpr>(m, "LinearExpr", kLinearExprClassDoc)
|
||||
// We make sure to keep the order of the overloads: LinearExpr* before
|
||||
// ExprOrValue as this is faster to parse and type check.
|
||||
.def_static("sum", (&LinearExpr::Sum), arg("exprs"), py::kw_only(),
|
||||
arg("constant") = 0.0, "Creates `sum(exprs) + constant`.",
|
||||
py::return_value_policy::automatic, py::keep_alive<0, 1>())
|
||||
.def_static("sum", &LinearExpr::MixedSum, arg("exprs"), py::kw_only(),
|
||||
arg("constant") = 0.0, "Creates `sum(exprs) + constant`.",
|
||||
py::return_value_policy::automatic, py::keep_alive<0, 1>())
|
||||
.def_static("weighted_sum", &SafeWeightedSum, arg("exprs"), arg("coeffs"),
|
||||
py::kw_only(), arg("constant") = 0.0,
|
||||
"Creates `sum(expressions[i] * coefficients[i]) + constant`.",
|
||||
py::return_value_policy::automatic, py::keep_alive<0, 1>())
|
||||
.def_static("weighted_sum", &SafeMixedWeightedSum, arg("exprs"),
|
||||
arg("coeffs"), py::kw_only(), arg("constant") = 0.0,
|
||||
"Creates `sum(expressions[i] * coefficients[i]) + constant`.",
|
||||
.def_static("sum", &SumArguments,
|
||||
"Creates `sum(expressions) [+ constant]`.",
|
||||
py::return_value_policy::automatic, py::keep_alive<0, 1>())
|
||||
.def_static(
|
||||
"weighted_sum", &WeightedSumArguments,
|
||||
"Creates `sum(expressions[i] * coefficients[i]) [+ constant]`.",
|
||||
arg("expressions"), arg("coefficients"), py::kw_only(),
|
||||
arg("constant") = 0.0, py::return_value_policy::automatic,
|
||||
py::keep_alive<0, 1>())
|
||||
.def_static("term", &LinearExpr::Term, arg("expr").none(false),
|
||||
arg("coeff"), "Returns expr * coeff.",
|
||||
py::return_value_policy::automatic, py::keep_alive<0, 1>())
|
||||
// Compatibility layer.
|
||||
.def_static("term", &LinearExpr::Affine, arg("expr").none(false),
|
||||
arg("coeff"), py::kw_only(), py::arg("constant"),
|
||||
"Returns expr * coeff [+ constant].",
|
||||
@@ -287,7 +354,7 @@ PYBIND11_MODULE(model_builder_helper, m) {
|
||||
.def("__repr__", &LinearExpr::DebugString)
|
||||
// Operators.
|
||||
// Note that we keep the 3 APIS (expr, int, double) instead of using an
|
||||
// ExprOrValue argument as this is more efficient.
|
||||
// py::handle argument as this is more efficient.
|
||||
.def("__add__", &LinearExpr::Add, arg("other").none(false),
|
||||
py::return_value_policy::automatic, py::keep_alive<0, 1>(),
|
||||
py::keep_alive<0, 2>())
|
||||
@@ -342,44 +409,44 @@ PYBIND11_MODULE(model_builder_helper, m) {
|
||||
py::return_value_policy::automatic, py::keep_alive<0, 1>())
|
||||
// Disable other operators as they are not supported.
|
||||
.def("__floordiv__",
|
||||
[](LinearExpr* /*self*/, ExprOrValue /*other*/) {
|
||||
[](LinearExpr* /*self*/, py::handle /*other*/) {
|
||||
ThrowError(PyExc_NotImplementedError,
|
||||
"calling // on a linear expression is not supported.");
|
||||
})
|
||||
.def("__mod__",
|
||||
[](LinearExpr* /*self*/, ExprOrValue /*other*/) {
|
||||
[](LinearExpr* /*self*/, py::handle /*other*/) {
|
||||
ThrowError(PyExc_NotImplementedError,
|
||||
"calling %% on a linear expression is not supported.");
|
||||
})
|
||||
.def("__pow__",
|
||||
[](LinearExpr* /*self*/, ExprOrValue /*other*/) {
|
||||
[](LinearExpr* /*self*/, py::handle /*other*/) {
|
||||
ThrowError(PyExc_NotImplementedError,
|
||||
"calling ** on a linear expression is not supported.");
|
||||
})
|
||||
.def("__lshift__",
|
||||
[](LinearExpr* /*self*/, ExprOrValue /*other*/) {
|
||||
[](LinearExpr* /*self*/, py::handle /*other*/) {
|
||||
ThrowError(
|
||||
PyExc_NotImplementedError,
|
||||
"calling left shift on a linear expression is not supported");
|
||||
})
|
||||
.def("__rshift__",
|
||||
[](LinearExpr* /*self*/, ExprOrValue /*other*/) {
|
||||
[](LinearExpr* /*self*/, py::handle /*other*/) {
|
||||
ThrowError(
|
||||
PyExc_NotImplementedError,
|
||||
"calling right shift on a linear expression is not supported");
|
||||
})
|
||||
.def("__and__",
|
||||
[](LinearExpr* /*self*/, ExprOrValue /*other*/) {
|
||||
[](LinearExpr* /*self*/, py::handle /*other*/) {
|
||||
ThrowError(PyExc_NotImplementedError,
|
||||
"calling and on a linear expression is not supported");
|
||||
})
|
||||
.def("__or__",
|
||||
[](LinearExpr* /*self*/, ExprOrValue /*other*/) {
|
||||
[](LinearExpr* /*self*/, py::handle /*other*/) {
|
||||
ThrowError(PyExc_NotImplementedError,
|
||||
"calling or on a linear expression is not supported");
|
||||
})
|
||||
.def("__xor__",
|
||||
[](LinearExpr* /*self*/, ExprOrValue /*other*/) {
|
||||
[](LinearExpr* /*self*/, py::handle /*other*/) {
|
||||
ThrowError(PyExc_NotImplementedError,
|
||||
"calling xor on a linear expression is not supported");
|
||||
})
|
||||
|
||||
@@ -619,6 +619,7 @@ class LinearBaseErrorsTest(absltest.TestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
|
||||
class UnknownLinearType(mb.LinearExpr):
|
||||
|
||||
def __init__(self):
|
||||
mb.LinearExpr.__init__(self)
|
||||
|
||||
|
||||
@@ -783,73 +783,6 @@ void ModelSolverHelper::SetSolverSpecificParameters(
|
||||
void ModelSolverHelper::EnableOutput(bool enabled) { solver_output_ = enabled; }
|
||||
|
||||
// Expressions.
|
||||
|
||||
LinearExpr* LinearExpr::Sum(const std::vector<LinearExpr*>& exprs,
|
||||
double constant) {
|
||||
if (exprs.empty()) {
|
||||
return new FixedValue(0.0);
|
||||
} else if (exprs.size() == 1) {
|
||||
return exprs[0];
|
||||
} else {
|
||||
return new SumArray(exprs, constant);
|
||||
}
|
||||
}
|
||||
|
||||
LinearExpr* LinearExpr::MixedSum(const std::vector<ExprOrValue>& exprs,
|
||||
double constant) {
|
||||
std::vector<LinearExpr*> lin_exprs;
|
||||
|
||||
for (const ExprOrValue& choice : exprs) {
|
||||
if (choice.expr != nullptr) {
|
||||
lin_exprs.push_back(choice.expr);
|
||||
} else {
|
||||
constant += choice.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: if there is only one term, return it.
|
||||
if (constant == 0.0 && lin_exprs.size() == 1) {
|
||||
return lin_exprs[0];
|
||||
}
|
||||
|
||||
if (lin_exprs.empty()) {
|
||||
return new FixedValue(constant);
|
||||
} else if (lin_exprs.size() == 1) {
|
||||
return new AffineExpr(lin_exprs[0], 1.0, constant);
|
||||
} else {
|
||||
return new SumArray(lin_exprs, constant);
|
||||
}
|
||||
}
|
||||
|
||||
LinearExpr* LinearExpr::WeightedSum(const std::vector<LinearExpr*>& exprs,
|
||||
const std::vector<double>& coeffs,
|
||||
double constant) {
|
||||
if (exprs.empty()) return new FixedValue(0.0);
|
||||
if (exprs.size() == 1) return Affine(exprs[0], coeffs[0], constant);
|
||||
return new WeightedSumArray(exprs, coeffs, constant);
|
||||
}
|
||||
|
||||
LinearExpr* LinearExpr::MixedWeightedSum(const std::vector<ExprOrValue>& exprs,
|
||||
const std::vector<double>& coeffs,
|
||||
double constant) {
|
||||
std::vector<LinearExpr*> lin_exprs;
|
||||
std::vector<double> lin_coeffs;
|
||||
for (int i = 0; i < exprs.size(); ++i) {
|
||||
if (exprs[i].expr != nullptr) {
|
||||
lin_exprs.push_back(exprs[i].expr);
|
||||
lin_coeffs.push_back(coeffs[i]);
|
||||
} else {
|
||||
constant += coeffs[i] * exprs[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
if (lin_exprs.empty()) return new FixedValue(constant);
|
||||
if (lin_exprs.size() == 1) {
|
||||
return Affine(lin_exprs[0], lin_coeffs[0], constant);
|
||||
}
|
||||
return new WeightedSumArray(lin_exprs, lin_coeffs, constant);
|
||||
}
|
||||
|
||||
LinearExpr* LinearExpr::Term(LinearExpr* expr, double coeff) {
|
||||
return new AffineExpr(expr, coeff, 0.0);
|
||||
}
|
||||
@@ -860,8 +793,8 @@ LinearExpr* LinearExpr::Affine(LinearExpr* expr, double coeff,
|
||||
return new AffineExpr(expr, coeff, constant);
|
||||
}
|
||||
|
||||
double LinearExpr::AffineCst(double value, double coeff, double constant) {
|
||||
return value * coeff + constant;
|
||||
LinearExpr* LinearExpr::AffineCst(double value, double coeff, double constant) {
|
||||
return new FixedValue(value * coeff + constant);
|
||||
}
|
||||
|
||||
LinearExpr* LinearExpr::Constant(double value) { return new FixedValue(value); }
|
||||
|
||||
@@ -43,16 +43,6 @@ class ModelBuilderHelper;
|
||||
class ModelSolverHelper;
|
||||
class Variable;
|
||||
|
||||
// A class to hold an linear expression or a constant.
|
||||
struct ExprOrValue {
|
||||
explicit ExprOrValue(LinearExpr* e) : expr(e) {}
|
||||
explicit ExprOrValue(double v) : value(v) {}
|
||||
explicit ExprOrValue(int64_t v) : value(static_cast<double>(v)) {}
|
||||
|
||||
LinearExpr* expr = nullptr;
|
||||
double value = 0.0;
|
||||
};
|
||||
|
||||
// A linear expression that can be either integer or floating point.
|
||||
class LinearExpr {
|
||||
public:
|
||||
@@ -61,19 +51,9 @@ class LinearExpr {
|
||||
virtual std::string ToString() const = 0;
|
||||
virtual std::string DebugString() const = 0;
|
||||
|
||||
static LinearExpr* Sum(const std::vector<LinearExpr*>& exprs,
|
||||
double constant = 0.0);
|
||||
static LinearExpr* MixedSum(const std::vector<ExprOrValue>& exprs,
|
||||
double offset = 0.0);
|
||||
static LinearExpr* WeightedSum(const std::vector<LinearExpr*>& exprs,
|
||||
const std::vector<double>& coeffs,
|
||||
double constant = 0.0);
|
||||
static LinearExpr* MixedWeightedSum(const std::vector<ExprOrValue>& exprs,
|
||||
const std::vector<double>& coeffs,
|
||||
double constant = 0.0);
|
||||
static LinearExpr* Term(LinearExpr* expr, double coeff);
|
||||
static LinearExpr* Affine(LinearExpr* expr, double coeff, double constant);
|
||||
static double AffineCst(double value, double coeff, double constant);
|
||||
static LinearExpr* AffineCst(double value, double coeff, double constant);
|
||||
static LinearExpr* Constant(double value);
|
||||
|
||||
LinearExpr* Add(LinearExpr* expr);
|
||||
|
||||
Reference in New Issue
Block a user