From 98593daf1020662a75b1af620591be4944f48978 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Sun, 19 Jan 2025 11:38:35 +0100 Subject: [PATCH] rewrite model builder pybind11 code to use shared_ptr; add in place optimizations --- .../python/model_builder_helper.cc | 517 ++++++++++-------- .../python/model_builder_test.py | 121 ++++ .../wrappers/model_builder_helper.cc | 240 ++++---- .../wrappers/model_builder_helper.h | 150 ++--- 4 files changed, 641 insertions(+), 387 deletions(-) diff --git a/ortools/linear_solver/python/model_builder_helper.cc b/ortools/linear_solver/python/model_builder_helper.cc index 0d65d87266..b2c27bf2ee 100644 --- a/ortools/linear_solver/python/model_builder_helper.cc +++ b/ortools/linear_solver/python/model_builder_helper.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -63,7 +64,6 @@ using ::operations_research::mb::Variable; using ::operations_research::mb::WeightedSumArray; namespace py = pybind11; -using ::py::arg; void ThrowError(PyObject* py_exception, const std::string& message) { PyErr_SetString(py_exception, message.c_str()); @@ -211,38 +211,30 @@ 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(arg)) { - expr = arg.cast(); - } else { - float_value = arg.cast(); - } -} - -LinearExpr* SumArguments(py::args args, const py::kwargs& kwargs) { - std::vector linear_exprs; +std::shared_ptr SumArguments(py::args args, + const py::kwargs& kwargs) { + std::vector> linear_exprs; double float_offset = 0.0; - const auto process_arg = [&](const py::handle& arg) -> void { - if (py::isinstance(arg)) { - linear_exprs.push_back(arg.cast()); - } else { - float_offset += arg.cast(); - } - }; - if (args.size() == 1 && py::isinstance(args[0])) { // Normal list or tuple argument. py::sequence elements = args[0].cast(); linear_exprs.reserve(elements.size()); for (const py::handle& arg : elements) { - process_arg(arg); + if (py::isinstance(arg)) { + linear_exprs.push_back(arg.cast>()); + } else { + float_offset += arg.cast(); + } } } else { // Direct sum(x, y, 3, ..) without []. linear_exprs.reserve(args.size()); for (const py::handle arg : args) { - process_arg(arg); + if (py::isinstance(arg)) { + linear_exprs.push_back(arg.cast>()); + } else { + float_offset += arg.cast(); + } } } @@ -259,21 +251,21 @@ LinearExpr* SumArguments(py::args args, const py::kwargs& kwargs) { } if (linear_exprs.empty()) { - return new FixedValue(float_offset); + return std::make_shared(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); + return std::make_shared(linear_exprs[0], 1.0, float_offset); } } else { - return new SumArray(linear_exprs, float_offset); + return std::make_shared(linear_exprs, float_offset); } } -LinearExpr* WeightedSumArguments(py::sequence expressions, - const std::vector& coefficients, - double offset = 0.0) { +std::shared_ptr WeightedSumArguments( + py::sequence expressions, const std::vector& coefficients, + double offset = 0.0) { if (expressions.size() != coefficients.size()) { ThrowError(PyExc_ValueError, absl::StrCat("LinearExpr::weighted_sum() requires the same " @@ -281,205 +273,269 @@ LinearExpr* WeightedSumArguments(py::sequence expressions, expressions.size(), " != ", coefficients.size())); } - std::vector linear_exprs; + std::vector> linear_exprs; std::vector 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; + std::shared_ptr expr = nullptr; + if (py::isinstance(arg)) { + if (coefficients[i] != 0.0) { + linear_exprs.push_back(arg.cast>()); + coeffs.push_back(coefficients[i]); + } + } else { + offset += arg.cast() * coefficients[i]; } } if (linear_exprs.empty()) { - return new FixedValue(offset); + return std::make_shared(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); - } + return LinearExpr::Affine(linear_exprs[0], coeffs[0], offset); } else { - return new WeightedSumArray(linear_exprs, coeffs, offset); + return std::make_shared(linear_exprs, coeffs, offset); } } PYBIND11_MODULE(model_builder_helper, m) { pybind11_protobuf::ImportNativeProtoCasters(); - py::class_(m, "LinearExpr", kLinearExprClassDoc) + py::class_>(m, "LinearExpr", + kLinearExprClassDoc) .def_static("sum", &SumArguments, - "Creates `sum(expressions) [+ constant]`.", - py::return_value_policy::automatic, py::keep_alive<0, 1>()) + "Creates `sum(expressions) [+ constant]`.") .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>()) - .def_static("term", &LinearExpr::Affine, arg("expr").none(false), - arg("coeff"), py::kw_only(), py::arg("constant"), - "Returns expr * coeff [+ constant].", - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def_static("term", &LinearExpr::AffineCst, arg("value"), arg("coeff"), - py::kw_only(), py::arg("constant"), - "Returns value * coeff [+ constant].", - py::return_value_policy::automatic) - .def_static("affine", &LinearExpr::Affine, arg("expr").none(false), - arg("coeff"), arg("constant") = 0.0, - "Returns expr * coeff + constant.", - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def_static("affine", &LinearExpr::AffineCst, arg("value"), arg("coeff"), - arg("constant") = 0.0, "Returns value * coeff + constant.", - py::return_value_policy::automatic) - .def_static("constant", &LinearExpr::Constant, arg("value"), - "Returns a constant linear expression.", - py::return_value_policy::automatic) + py::arg("expressions"), py::arg("coefficients"), py::kw_only(), + py::arg("constant") = 0.0) + .def_static("term", &LinearExpr::Term, py::arg("expr").none(false), + py::arg("coeff"), "Returns expr * coeff.") + .def_static("term", &LinearExpr::Affine, py::arg("expr").none(false), + py::arg("coeff"), py::kw_only(), py::arg("constant"), + "Returns expr * coeff [+ constant].") + .def_static("term", &LinearExpr::AffineCst, py::arg("value"), + py::arg("coeff"), py::kw_only(), py::arg("constant"), + "Returns value * coeff [+ constant].") + .def_static("affine", &LinearExpr::Affine, py::arg("expr").none(false), + py::arg("coeff"), py::arg("constant") = 0.0, + "Returns expr * coeff + constant.") + .def_static("affine", &LinearExpr::AffineCst, py::arg("value"), + py::arg("coeff"), py::arg("constant") = 0.0, + "Returns value * coeff + constant.") + .def_static("constant", &LinearExpr::Constant, py::arg("value"), + "Returns a constant linear expression.") // Methods. .def("__str__", &LinearExpr::ToString) .def("__repr__", &LinearExpr::DebugString) // Operators. - // Note that we keep the 3 APIS (expr, int, double) instead of using an - // 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>()) - .def("__add__", &LinearExpr::AddFloat, arg("cst"), - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def("__radd__", &LinearExpr::AddFloat, arg("cst"), - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def("__sub__", &LinearExpr::Sub, arg("other").none(false), - py::return_value_policy::automatic, py::keep_alive<0, 1>(), - py::keep_alive<0, 2>()) - .def("__sub__", &LinearExpr::SubFloat, arg("cst"), - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def("__rsub__", &LinearExpr::RSubFloat, arg("cst"), - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def("__mul__", &LinearExpr::MulFloat, arg("cst"), - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def("__rmul__", &LinearExpr::MulFloat, arg("cst"), - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def( - "__truediv__", - [](LinearExpr* self, double cst) { - if (cst == 0.0) { - ThrowError(PyExc_ZeroDivisionError, - "Division by zero is not supported."); - } - return self->MulFloat(1.0 / cst); - }, - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def("__neg__", &LinearExpr::Neg, py::return_value_policy::automatic, - py::keep_alive<0, 1>()) + .def("__add__", &LinearExpr::Add, py::arg("other").none(false)) + .def("__add__", &LinearExpr::AddFloat, py::arg("cst")) + .def("__radd__", &LinearExpr::AddFloat, py::arg("cst")) + .def("__sub__", &LinearExpr::Sub, py::arg("other").none(false)) + .def("__sub__", &LinearExpr::SubFloat, py::arg("cst")) + .def("__rsub__", &LinearExpr::RSubFloat, py::arg("cst")) + .def("__mul__", &LinearExpr::MulFloat, py::arg("cst")) + .def("__rmul__", &LinearExpr::MulFloat, py::arg("cst")) + .def("__truediv__", + [](std::shared_ptr self, double cst) { + if (cst == 0.0) { + ThrowError(PyExc_ZeroDivisionError, + "Division by zero is not supported."); + } + return self->MulFloat(1.0 / cst); + }) + .def("__neg__", &LinearExpr::Neg) // Comparison operators. - .def("__eq__", &LinearExpr::Eq, arg("other").none(false), - "Creates the constraint `self == other`.", - py::return_value_policy::automatic, py::keep_alive<0, 1>(), - py::keep_alive<0, 2>()) - .def("__eq__", &LinearExpr::EqCst, arg("cst"), - "Creates the constraint `self == cst`.", - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def("__le__", &LinearExpr::Le, arg("other").none(false), - "Creates the constraint `self <= other`.", - py::return_value_policy::automatic, py::keep_alive<0, 1>(), - py::keep_alive<0, 2>()) - .def("__le__", &LinearExpr::LeCst, arg("cst"), - "Creates the constraint `self <= cst`.", - py::return_value_policy::automatic, py::keep_alive<0, 1>()) - .def("__ge__", &LinearExpr::Ge, arg("other").none(false), - "Creates the constraint `self >= other`.", - py::return_value_policy::automatic, py::keep_alive<0, 1>(), - py::keep_alive<0, 2>()) - .def("__ge__", &LinearExpr::GeCst, arg("cst"), - "Creates the constraint `self >= cst`.", - py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__eq__", &LinearExpr::Eq, py::arg("other").none(false), + "Creates the constraint `self == other`.") + .def("__eq__", &LinearExpr::EqCst, py::arg("cst"), + "Creates the constraint `self == cst`.") + .def("__le__", &LinearExpr::Le, py::arg("other").none(false), + "Creates the constraint `self <= other`.") + .def("__le__", &LinearExpr::LeCst, py::arg("cst"), + "Creates the constraint `self <= cst`.") + .def("__ge__", &LinearExpr::Ge, py::arg("other").none(false), + "Creates the constraint `self >= other`.") + .def("__ge__", &LinearExpr::GeCst, py::arg("cst"), + "Creates the constraint `self >= cst`.") // Disable other operators as they are not supported. .def("__floordiv__", - [](LinearExpr* /*self*/, py::handle /*other*/) { + [](std::shared_ptr /*self*/, py::handle /*other*/) { ThrowError(PyExc_NotImplementedError, "calling // on a linear expression is not supported."); }) .def("__mod__", - [](LinearExpr* /*self*/, py::handle /*other*/) { + [](std::shared_ptr /*self*/, py::handle /*other*/) { ThrowError(PyExc_NotImplementedError, "calling %% on a linear expression is not supported."); }) .def("__pow__", - [](LinearExpr* /*self*/, py::handle /*other*/) { + [](std::shared_ptr /*self*/, py::handle /*other*/) { ThrowError(PyExc_NotImplementedError, "calling ** on a linear expression is not supported."); }) .def("__lshift__", - [](LinearExpr* /*self*/, py::handle /*other*/) { + [](std::shared_ptr /*self*/, py::handle /*other*/) { ThrowError( PyExc_NotImplementedError, "calling left shift on a linear expression is not supported"); }) .def("__rshift__", - [](LinearExpr* /*self*/, py::handle /*other*/) { + [](std::shared_ptr /*self*/, py::handle /*other*/) { ThrowError( PyExc_NotImplementedError, "calling right shift on a linear expression is not supported"); }) .def("__and__", - [](LinearExpr* /*self*/, py::handle /*other*/) { + [](std::shared_ptr /*self*/, py::handle /*other*/) { ThrowError(PyExc_NotImplementedError, "calling and on a linear expression is not supported"); }) .def("__or__", - [](LinearExpr* /*self*/, py::handle /*other*/) { + [](std::shared_ptr /*self*/, py::handle /*other*/) { ThrowError(PyExc_NotImplementedError, "calling or on a linear expression is not supported"); }) .def("__xor__", - [](LinearExpr* /*self*/, py::handle /*other*/) { + [](std::shared_ptr /*self*/, py::handle /*other*/) { ThrowError(PyExc_NotImplementedError, "calling xor on a linear expression is not supported"); }) .def("__abs__", - [](LinearExpr* /*self*/) { + [](std::shared_ptr /*self*/) { ThrowError( PyExc_NotImplementedError, "calling abs() on a linear expression is not supported."); }) - .def("__bool__", [](LinearExpr* /*self*/) { + .def("__bool__", [](std::shared_ptr /*self*/) { ThrowError(PyExc_NotImplementedError, "Evaluating a LinearExpr instance as a Boolean is " "not supported."); }); // Expose Internal classes, mostly for testing. - py::class_(m, "FlatExpr") - .def(py::init()) - .def(py::init()) - .def(py::init&, - const std::vector&, double>(), - py::keep_alive<1, 2>()) + py::class_, LinearExpr>(m, "FlatExpr") + .def(py::init>()) + .def(py::init, std::shared_ptr>()) + .def(py::init>&, + const std::vector&, double>()) .def(py::init()) .def_property_readonly("vars", &FlatExpr::vars) .def("variable_indices", &FlatExpr::VarIndices) .def_property_readonly("coeffs", &FlatExpr::coeffs) .def_property_readonly("offset", &FlatExpr::offset); - py::class_(m, "AffineExpr") - .def(py::init()) + py::class_, LinearExpr>( + m, "SumArray", "Holds a sum of linear expressions, and constants.") + .def(py::init>, double>()) + .def( + "__add__", + [](py::object self, + std::shared_ptr other) -> std::shared_ptr { + const int num_uses = Py_REFCNT(self.ptr()); + std::shared_ptr expr = + self.cast>(); + if (num_uses == 4) { + expr->AddInPlace(other); + return expr; + } + return expr->Add(other); + }, + py::arg("other").none(false), + "Returns the sum of `self` and `other`.") + .def( + "__add__", + [](py::object self, double cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(self.ptr()); + std::shared_ptr expr = + self.cast>(); + if (num_uses == 4) { + expr->AddFloatInPlace(cst); + return expr; + } + return expr->AddFloat(cst); + }, + py::arg("cst"), "Returns `self` + `cst`.") + .def("__radd__", &LinearExpr::Add, py::arg("other").none(false), + "Returns `self` + `other`.") + .def( + "__radd__", + [](py::object self, double cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(self.ptr()); + std::shared_ptr expr = + self.cast>(); + if (num_uses == 4) { + expr->AddFloatInPlace(cst); + return expr; + } + return expr->AddFloat(cst); + }, + py::arg("cst"), "Returns `self` + `cst`.") + .def( + "__sub__", + [](py::object self, + std::shared_ptr other) -> std::shared_ptr { + const int num_uses = Py_REFCNT(self.ptr()); + std::shared_ptr expr = + self.cast>(); + if (num_uses == 4) { + expr->AddInPlace(other->Neg()); + return expr; + } + return expr->Sub(other); + }, + py::arg("other").none(false), "Returns `self` - `other`.") + .def( + "__sub__", + [](py::object self, double cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(self.ptr()); + std::shared_ptr expr = + self.cast>(); + if (num_uses == 4) { + expr->AddFloatInPlace(-cst); + return expr; + } + return expr->SubFloat(cst); + }, + py::arg("cst"), "Returns `self` - `cst`.") + .def_property_readonly( + "num_exprs", &SumArray::num_exprs, + "Returns the number of linear expressions in the sum.") + .def_property_readonly("offset", &SumArray::offset, + "Returns the offset of the sum."); + + py::class_, LinearExpr>(m, + "AffineExpr") + .def(py::init, double, double>()) + .def("__add__", &AffineExpr::Add, py::arg("other").none(false), + "Returns `self` + `other`.") + .def("__add__", &AffineExpr::AddFloat, py::arg("cst"), + "Returns `self` + `cst`.") + .def("__radd__", &AffineExpr::Add, py::arg("other").none(false), + "Returns `self` + `other`.") + .def("__radd__", &AffineExpr::AddFloat, py::arg("cst"), + "Returns `self` + `cst`.") + .def("__sub__", &AffineExpr::Sub, py::arg("other").none(false), + "Returns `self` - `other`.") + .def("__sub__", &AffineExpr::SubFloat, py::arg("cst"), + "Returns `self` - `cst`.") + .def("__rsub__", &AffineExpr::RSubFloat, py::arg("cst"), + "Returns `cst` - `self`.") + .def("__mul__", &AffineExpr::MulFloat, py::arg("cst"), + "Returns `self` * `cst`.") + .def("__rmul__", &AffineExpr::MulFloat, py::arg("cst"), + "Returns `self` * `cst`.") + .def("__neg__", &AffineExpr::Neg, "Returns -`self`.") .def_property_readonly("expression", &AffineExpr ::expression) .def_property_readonly("coefficient", &AffineExpr::coefficient) .def_property_readonly("offset", &AffineExpr::offset); - py::class_(m, "Variable", kVarClassDoc) + py::class_, LinearExpr>(m, "Variable", + kVarClassDoc) .def(py::init()) .def(py::init()) .def(py::init()) @@ -505,11 +561,14 @@ PYBIND11_MODULE(model_builder_helper, m) { return absl::HashOf(std::make_tuple(self.helper(), self.index())); }); - py::class_(m, "BoundedLinearExpression") - .def(py::init()) - .def(py::init()) - .def(py::init()) - .def(py::init()) + py::class_>( + m, "BoundedLinearExpression") + .def(py::init, double, double>()) + .def(py::init, std::shared_ptr, + double, double>()) + .def(py::init, int64_t, int64_t>()) + .def(py::init, std::shared_ptr, + int64_t, int64_t>()) .def_property_readonly("vars", &BoundedLinearExpression::vars) .def_property_readonly("coeffs", &BoundedLinearExpression::coeffs) .def_property_readonly("lower_bound", @@ -531,7 +590,7 @@ PYBIND11_MODULE(model_builder_helper, m) { .def("__str__", &BoundedLinearExpression::ToString) .def("__repr__", &BoundedLinearExpression::DebugString); - m.def("to_mpmodel_proto", &ToMPModelProto, arg("helper")); + m.def("to_mpmodel_proto", &ToMPModelProto, py::arg("helper")); py::class_(m, "MPModelExportOptions") .def(py::init<>()) @@ -545,26 +604,26 @@ PYBIND11_MODULE(model_builder_helper, m) { py::class_(m, "ModelBuilderHelper") .def(py::init<>()) .def("overwrite_model", &ModelBuilderHelper::OverwriteModel, - arg("other_helper")) + py::arg("other_helper")) .def("export_to_mps_string", &ModelBuilderHelper::ExportToMpsString, - arg("options") = MPModelExportOptions()) + py::arg("options") = MPModelExportOptions()) .def("export_to_lp_string", &ModelBuilderHelper::ExportToLpString, - arg("options") = MPModelExportOptions()) + py::arg("options") = MPModelExportOptions()) .def("write_to_mps_file", &ModelBuilderHelper::WriteToMpsFile, - arg("filename"), arg("options") = MPModelExportOptions()) + py::arg("filename"), py::arg("options") = MPModelExportOptions()) .def("read_model_from_proto_file", - &ModelBuilderHelper::ReadModelFromProtoFile, arg("filename")) + &ModelBuilderHelper::ReadModelFromProtoFile, py::arg("filename")) .def("write_model_to_proto_file", - &ModelBuilderHelper::WriteModelToProtoFile, arg("filename")) + &ModelBuilderHelper::WriteModelToProtoFile, py::arg("filename")) .def("import_from_mps_string", &ModelBuilderHelper::ImportFromMpsString, - arg("mps_string")) + py::arg("mps_string")) .def("import_from_mps_file", &ModelBuilderHelper::ImportFromMpsFile, - arg("mps_file")) + py::arg("mps_file")) #if defined(USE_LP_PARSER) .def("import_from_lp_string", &ModelBuilderHelper::ImportFromLpString, - arg("lp_string")) + py::arg("lp_string")) .def("import_from_lp_file", &ModelBuilderHelper::ImportFromLpFile, - arg("lp_file")) + py::arg("lp_file")) #else .def("import_from_lp_string", [](const std::string& lp_string) { LOG(INFO) << "Parsing LP string is not compiled in"; @@ -588,9 +647,9 @@ PYBIND11_MODULE(model_builder_helper, m) { constraint_upper_bounds, constraint_matrix, helper->mutable_model()); }, - arg("variable_lower_bound"), arg("variable_upper_bound"), - arg("objective_coefficients"), arg("constraint_lower_bounds"), - arg("constraint_upper_bounds"), arg("constraint_matrix")) + py::arg("variable_lower_bound"), py::arg("variable_upper_bound"), + py::arg("objective_coefficients"), py::arg("constraint_lower_bounds"), + py::arg("constraint_upper_bounds"), py::arg("constraint_matrix")) .def("add_var", &ModelBuilderHelper::AddVar) .def("add_var_array", [](ModelBuilderHelper* helper, std::vector shape, double lb, @@ -651,14 +710,14 @@ PYBIND11_MODULE(model_builder_helper, m) { return result; }) .def("set_var_lower_bound", &ModelBuilderHelper::SetVarLowerBound, - arg("var_index"), arg("lb")) + py::arg("var_index"), py::arg("lb")) .def("set_var_upper_bound", &ModelBuilderHelper::SetVarUpperBound, - arg("var_index"), arg("ub")) + py::arg("var_index"), py::arg("ub")) .def("set_var_integrality", &ModelBuilderHelper::SetVarIntegrality, - arg("var_index"), arg("is_integer")) + py::arg("var_index"), py::arg("is_integer")) .def("set_var_objective_coefficient", - &ModelBuilderHelper::SetVarObjectiveCoefficient, arg("var_index"), - arg("coeff")) + &ModelBuilderHelper::SetVarObjectiveCoefficient, + py::arg("var_index"), py::arg("coeff")) .def("set_objective_coefficients", [](ModelBuilderHelper* helper, const std::vector& indices, const std::vector& coefficients) { @@ -667,29 +726,29 @@ PYBIND11_MODULE(model_builder_helper, m) { helper->SetVarObjectiveCoefficient(i, c); } }) - .def("set_var_name", &ModelBuilderHelper::SetVarName, arg("var_index"), - arg("name")) + .def("set_var_name", &ModelBuilderHelper::SetVarName, + py::arg("var_index"), py::arg("name")) .def("var_lower_bound", &ModelBuilderHelper::VarLowerBound, - arg("var_index")) + py::arg("var_index")) .def("var_upper_bound", &ModelBuilderHelper::VarUpperBound, - arg("var_index")) + py::arg("var_index")) .def("var_is_integral", &ModelBuilderHelper::VarIsIntegral, - arg("var_index")) + py::arg("var_index")) .def("var_objective_coefficient", - &ModelBuilderHelper::VarObjectiveCoefficient, arg("var_index")) - .def("var_name", &ModelBuilderHelper::VarName, arg("var_index")) + &ModelBuilderHelper::VarObjectiveCoefficient, py::arg("var_index")) + .def("var_name", &ModelBuilderHelper::VarName, py::arg("var_index")) .def("add_linear_constraint", &ModelBuilderHelper::AddLinearConstraint) .def("set_constraint_lower_bound", - &ModelBuilderHelper::SetConstraintLowerBound, arg("ct_index"), - arg("lb")) + &ModelBuilderHelper::SetConstraintLowerBound, py::arg("ct_index"), + py::arg("lb")) .def("set_constraint_upper_bound", - &ModelBuilderHelper::SetConstraintUpperBound, arg("ct_index"), - arg("ub")) + &ModelBuilderHelper::SetConstraintUpperBound, py::arg("ct_index"), + py::arg("ub")) .def("add_term_to_constraint", &ModelBuilderHelper::AddConstraintTerm, - arg("ct_index"), arg("var_index"), arg("coeff")) + py::arg("ct_index"), py::arg("var_index"), py::arg("coeff")) .def("add_terms_to_constraint", [](ModelBuilderHelper* helper, int ct_index, - const std::vector& vars, + const std::vector>& vars, const std::vector& coefficients) { for (int i = 0; i < vars.size(); ++i) { helper->AddConstraintTerm(ct_index, vars[i]->index(), @@ -697,39 +756,39 @@ PYBIND11_MODULE(model_builder_helper, m) { } }) .def("safe_add_term_to_constraint", - &ModelBuilderHelper::SafeAddConstraintTerm, arg("ct_index"), - arg("var_index"), arg("coeff")) + &ModelBuilderHelper::SafeAddConstraintTerm, py::arg("ct_index"), + py::arg("var_index"), py::arg("coeff")) .def("set_constraint_name", &ModelBuilderHelper::SetConstraintName, - arg("ct_index"), arg("name")) + py::arg("ct_index"), py::arg("name")) .def("set_constraint_coefficient", - &ModelBuilderHelper::SetConstraintCoefficient, arg("ct_index"), - arg("var_index"), arg("coeff")) + &ModelBuilderHelper::SetConstraintCoefficient, py::arg("ct_index"), + py::arg("var_index"), py::arg("coeff")) .def("constraint_lower_bound", &ModelBuilderHelper::ConstraintLowerBound, - arg("ct_index")) + py::arg("ct_index")) .def("constraint_upper_bound", &ModelBuilderHelper::ConstraintUpperBound, - arg("ct_index")) + py::arg("ct_index")) .def("constraint_name", &ModelBuilderHelper::ConstraintName, - arg("ct_index")) + py::arg("ct_index")) .def("constraint_var_indices", &ModelBuilderHelper::ConstraintVarIndices, - arg("ct_index")) + py::arg("ct_index")) .def("constraint_coefficients", - &ModelBuilderHelper::ConstraintCoefficients, arg("ct_index")) + &ModelBuilderHelper::ConstraintCoefficients, py::arg("ct_index")) .def("add_enforced_linear_constraint", &ModelBuilderHelper::AddEnforcedLinearConstraint) .def("is_enforced_linear_constraint", &ModelBuilderHelper::IsEnforcedConstraint) .def("set_enforced_constraint_lower_bound", &ModelBuilderHelper::SetEnforcedConstraintLowerBound, - arg("ct_index"), arg("lb")) + py::arg("ct_index"), py::arg("lb")) .def("set_enforced_constraint_upper_bound", &ModelBuilderHelper::SetEnforcedConstraintUpperBound, - arg("ct_index"), arg("ub")) + py::arg("ct_index"), py::arg("ub")) .def("add_term_to_enforced_constraint", - &ModelBuilderHelper::AddEnforcedConstraintTerm, arg("ct_index"), - arg("var_index"), arg("coeff")) + &ModelBuilderHelper::AddEnforcedConstraintTerm, py::arg("ct_index"), + py::arg("var_index"), py::arg("coeff")) .def("add_terms_to_enforced_constraint", [](ModelBuilderHelper* helper, int ct_index, - const std::vector& vars, + const std::vector>& vars, const std::vector& coefficients) { for (int i = 0; i < vars.size(); ++i) { helper->AddEnforcedConstraintTerm(ct_index, vars[i]->index(), @@ -737,47 +796,53 @@ PYBIND11_MODULE(model_builder_helper, m) { } }) .def("safe_add_term_to_enforced_constraint", - &ModelBuilderHelper::SafeAddEnforcedConstraintTerm, arg("ct_index"), - arg("var_index"), arg("coeff")) + &ModelBuilderHelper::SafeAddEnforcedConstraintTerm, + py::arg("ct_index"), py::arg("var_index"), py::arg("coeff")) .def("set_enforced_constraint_name", - &ModelBuilderHelper::SetEnforcedConstraintName, arg("ct_index"), - arg("name")) + &ModelBuilderHelper::SetEnforcedConstraintName, py::arg("ct_index"), + py::arg("name")) .def("set_enforced_constraint_coefficient", &ModelBuilderHelper::SetEnforcedConstraintCoefficient, - arg("ct_index"), arg("var_index"), arg("coeff")) + py::arg("ct_index"), py::arg("var_index"), py::arg("coeff")) .def("enforced_constraint_lower_bound", - &ModelBuilderHelper::EnforcedConstraintLowerBound, arg("ct_index")) + &ModelBuilderHelper::EnforcedConstraintLowerBound, + py::arg("ct_index")) .def("enforced_constraint_upper_bound", - &ModelBuilderHelper::EnforcedConstraintUpperBound, arg("ct_index")) + &ModelBuilderHelper::EnforcedConstraintUpperBound, + py::arg("ct_index")) .def("enforced_constraint_name", - &ModelBuilderHelper::EnforcedConstraintName, arg("ct_index")) + &ModelBuilderHelper::EnforcedConstraintName, py::arg("ct_index")) .def("enforced_constraint_var_indices", - &ModelBuilderHelper::EnforcedConstraintVarIndices, arg("ct_index")) + &ModelBuilderHelper::EnforcedConstraintVarIndices, + py::arg("ct_index")) .def("enforced_constraint_coefficients", - &ModelBuilderHelper::EnforcedConstraintCoefficients, arg("ct_index")) + &ModelBuilderHelper::EnforcedConstraintCoefficients, + py::arg("ct_index")) .def("set_enforced_constraint_indicator_variable_index", &ModelBuilderHelper::SetEnforcedIndicatorVariableIndex, - arg("ct_index"), arg("var_index")) + py::arg("ct_index"), py::arg("var_index")) .def("set_enforced_constraint_indicator_value", - &ModelBuilderHelper::SetEnforcedIndicatorValue, arg("ct_index"), - arg("positive")) + &ModelBuilderHelper::SetEnforcedIndicatorValue, py::arg("ct_index"), + py::arg("positive")) .def("enforced_constraint_indicator_variable_index", - &ModelBuilderHelper::EnforcedIndicatorVariableIndex, arg("ct_index")) + &ModelBuilderHelper::EnforcedIndicatorVariableIndex, + py::arg("ct_index")) .def("enforced_constraint_indicator_value", - &ModelBuilderHelper::EnforcedIndicatorValue, arg("ct_index")) + &ModelBuilderHelper::EnforcedIndicatorValue, py::arg("ct_index")) .def("num_variables", &ModelBuilderHelper::num_variables) .def("num_constraints", &ModelBuilderHelper::num_constraints) .def("name", &ModelBuilderHelper::name) - .def("set_name", &ModelBuilderHelper::SetName, arg("name")) + .def("set_name", &ModelBuilderHelper::SetName, py::arg("name")) .def("clear_objective", &ModelBuilderHelper::ClearObjective) .def("maximize", &ModelBuilderHelper::maximize) - .def("set_maximize", &ModelBuilderHelper::SetMaximize, arg("maximize")) + .def("set_maximize", &ModelBuilderHelper::SetMaximize, + py::arg("maximize")) .def("set_objective_offset", &ModelBuilderHelper::SetObjectiveOffset, - arg("offset")) + py::arg("offset")) .def("objective_offset", &ModelBuilderHelper::ObjectiveOffset) .def("clear_hints", &ModelBuilderHelper::ClearHints) - .def("add_hint", &ModelBuilderHelper::AddHint, arg("var_index"), - arg("var_value")); + .def("add_hint", &ModelBuilderHelper::AddHint, py::arg("var_index"), + py::arg("var_value")); py::enum_(m, "SolveStatus") .value("OPTIMAL", SolveStatus::OPTIMAL) @@ -799,7 +864,7 @@ PYBIND11_MODULE(model_builder_helper, m) { py::class_(m, "ModelSolverHelper") .def(py::init()) .def("solver_is_supported", &ModelSolverHelper::SolverIsSupported) - .def("solve", &ModelSolverHelper::Solve, arg("model"), + .def("solve", &ModelSolverHelper::Solve, py::arg("model"), // The GIL is released during the solve to allow Python threads to do // other things in parallel, e.g., log and interrupt. py::call_guard()) @@ -829,11 +894,11 @@ PYBIND11_MODULE(model_builder_helper, m) { .def("set_log_callback", &ModelSolverHelper::SetLogCallback) .def("clear_log_callback", &ModelSolverHelper::ClearLogCallback) .def("set_time_limit_in_seconds", - &ModelSolverHelper::SetTimeLimitInSeconds, arg("limit")) + &ModelSolverHelper::SetTimeLimitInSeconds, py::arg("limit")) .def("set_solver_specific_parameters", &ModelSolverHelper::SetSolverSpecificParameters, - arg("solver_specific_parameters")) - .def("enable_output", &ModelSolverHelper::EnableOutput, arg("output")) + py::arg("solver_specific_parameters")) + .def("enable_output", &ModelSolverHelper::EnableOutput, py::arg("output")) .def("has_solution", &ModelSolverHelper::has_solution) .def("has_response", &ModelSolverHelper::has_response) .def("response", &ModelSolverHelper::response) @@ -844,18 +909,20 @@ PYBIND11_MODULE(model_builder_helper, m) { .def("objective_value", &ModelSolverHelper::objective_value) .def("best_objective_bound", &ModelSolverHelper::best_objective_bound) .def("variable_value", &ModelSolverHelper::variable_value, - arg("var_index")) + py::arg("var_index")) .def("expression_value", - [](const ModelSolverHelper& helper, LinearExpr* expr) { + [](const ModelSolverHelper& helper, + std::shared_ptr expr) { if (!helper.has_response()) { throw std::logic_error( "Accessing a solution value when none has been found."); } return helper.expression_value(expr); }) - .def("reduced_cost", &ModelSolverHelper::reduced_cost, arg("var_index")) - .def("dual_value", &ModelSolverHelper::dual_value, arg("ct_index")) - .def("activity", &ModelSolverHelper::activity, arg("ct_index")) + .def("reduced_cost", &ModelSolverHelper::reduced_cost, + py::arg("var_index")) + .def("dual_value", &ModelSolverHelper::dual_value, py::arg("ct_index")) + .def("activity", &ModelSolverHelper::activity, py::arg("ct_index")) .def("variable_values", [](const ModelSolverHelper& helper) { if (!helper.has_response()) { diff --git a/ortools/linear_solver/python/model_builder_test.py b/ortools/linear_solver/python/model_builder_test.py index 7fb0019916..9095492c2e 100644 --- a/ortools/linear_solver/python/model_builder_test.py +++ b/ortools/linear_solver/python/model_builder_test.py @@ -2243,6 +2243,127 @@ class ModelBuilderExamplesTest(absltest.TestCase): self.assertEqual(z.index, ct.indicator_variable.index) self.assertFalse(ct.indicator_value) + def testInPlaceSumModifications(self) -> None: + model = mb.Model() + x = [model.new_int_var(0, 10, f"x{i}") for i in range(5)] + y = [model.new_int_var(0, 10, f"y{i}") for i in range(5)] + e1 = sum(x) + self.assertIsInstance(e1, mbh.SumArray) + self.assertEqual(e1.offset, 0) + self.assertEqual(e1.num_exprs, 5) + e1_str = str(e1) + _ = e1 + y[0] + _ = sum(y) + e1 + self.assertEqual(e1_str, str(e1)) + + e2 = sum(x) - 2 - y[0] - 0.1 + e2_str = str(e2) + self.assertIsInstance(e2, mbh.SumArray) + self.assertEqual(e2.offset, -2.1) + self.assertEqual(e2.num_exprs, 6) + _ = e2 + 2.5 + self.assertEqual(str(e2), e2_str) + + e3 = 1.2 + sum(x) + 0.3 + self.assertIsInstance(e3, mbh.SumArray) + self.assertEqual(e3.offset, 1.5) + self.assertEqual(e3.num_exprs, 5) + + def testLargeSum(self) -> None: + model = mb.Model() + x = [model.new_int_var(0, 10, f"x{i}") for i in range(100000)] + model.add(sum(x) == 10) + + def testSimplification1(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = (x * 2) * 2 + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(4, prod.coefficient) + self.assertEqual(0, prod.offset) + + def testSimplification2(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = 2 * (x * 2) + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(4, prod.coefficient) + self.assertEqual(0, prod.offset) + + def testSimplification3(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = (2 * x) * 2 + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(4, prod.coefficient) + self.assertEqual(0, prod.offset) + + def testSimplification4(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = 2 * (2 * x) + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(4, prod.coefficient) + self.assertEqual(0, prod.offset) + + def testSimplification5(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = 2 * (x + 1) + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(2, prod.coefficient) + self.assertEqual(2, prod.offset) + + def testSimplification6(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = (x + 1) * 2 + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(2, prod.coefficient) + self.assertEqual(2, prod.offset) + + def testSimplification7(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = 2 * (x - 1) + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(2, prod.coefficient) + self.assertEqual(-2, prod.offset) + + def testSimplification8(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = (x - 1) * 2 + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(2, prod.coefficient) + self.assertEqual(-2, prod.offset) + + def testSimplification9(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = 2 * (1 - x) + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(-2, prod.coefficient) + self.assertEqual(2, prod.offset) + + def testSimplification10(self): + model = mb.Model() + x = model.new_int_var(-10, 10, "x") + prod = (1 - x) * 2 + self.assertIsInstance(prod, mbh.AffineExpr) + self.assertEqual(x, prod.expression) + self.assertEqual(-2, prod.coefficient) + self.assertEqual(2, prod.offset) + if __name__ == "__main__": absltest.main() diff --git a/ortools/linear_solver/wrappers/model_builder_helper.cc b/ortools/linear_solver/wrappers/model_builder_helper.cc index 688350f93c..c03c8a22d1 100644 --- a/ortools/linear_solver/wrappers/model_builder_helper.cc +++ b/ortools/linear_solver/wrappers/model_builder_helper.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -250,7 +251,7 @@ void ModelBuilderHelper::SetConstraintCoefficient(int ct_index, int var_index, } } // If we reach this point, the variable does not exist in the constraint yet, - // so we add it to the constraint as a new term. + // so we add it to the constraint as a newterm. ct_proto->add_var_index(var_index); ct_proto->add_coefficient(coeff); } @@ -280,7 +281,7 @@ std::vector ModelBuilderHelper::ConstraintCoefficients( int ModelBuilderHelper::AddEnforcedLinearConstraint() { const int index = model_.general_constraint_size(); - // Create the new general constraint, and force the type to indicator ct. + // Create the mew constraint, and force the type to indicator ct. model_.add_general_constraint()->mutable_indicator_constraint(); return index; } @@ -716,7 +717,8 @@ double ModelSolverHelper::variable_value(int var_index) const { return response_.value().variable_value(var_index); } -double ModelSolverHelper::expression_value(LinearExpr* expr) const { +double ModelSolverHelper::expression_value( + std::shared_ptr expr) const { if (!has_response()) return 0.0; evaluator_.Clear(); evaluator_.AddToProcess(expr, 1.0); @@ -783,55 +785,68 @@ void ModelSolverHelper::SetSolverSpecificParameters( void ModelSolverHelper::EnableOutput(bool enabled) { solver_output_ = enabled; } // Expressions. -LinearExpr* LinearExpr::Term(LinearExpr* expr, double coeff) { - return new AffineExpr(expr, coeff, 0.0); +std::shared_ptr LinearExpr::Term(std::shared_ptr expr, + double coeff) { + return std::make_shared(expr, coeff, 0.0); } -LinearExpr* LinearExpr::Affine(LinearExpr* expr, double coeff, - double constant) { +std::shared_ptr LinearExpr::Affine(std::shared_ptr expr, + double coeff, double constant) { if (coeff == 1.0 && constant == 0.0) return expr; - return new AffineExpr(expr, coeff, constant); + return std::make_shared(expr, coeff, constant); } -LinearExpr* LinearExpr::AffineCst(double value, double coeff, double constant) { - return new FixedValue(value * coeff + constant); +std::shared_ptr LinearExpr::AffineCst(double value, double coeff, + double constant) { + return std::make_shared(value * coeff + constant); } -LinearExpr* LinearExpr::Constant(double value) { return new FixedValue(value); } - -LinearExpr* LinearExpr::Add(LinearExpr* expr) { - return new SumArray({this, expr}, 0.0); +std::shared_ptr LinearExpr::Constant(double value) { + return std::make_shared(value); } -LinearExpr* LinearExpr::AddFloat(double cst) { - if (cst == 0.0) return this; - return new AffineExpr(this, 1.0, cst); +std::shared_ptr LinearExpr::Add(std::shared_ptr expr) { + std::vector> exprs; + exprs.push_back(shared_from_this()); + exprs.push_back(expr); + return std::make_shared(exprs, 0.0); } -LinearExpr* LinearExpr::Sub(LinearExpr* expr) { - return new WeightedSumArray({this, expr}, {1, -1}, 0.0); +std::shared_ptr LinearExpr::AddFloat(double cst) { + if (cst == 0.0) return shared_from_this(); + return std::make_shared(shared_from_this(), 1.0, cst); } -LinearExpr* LinearExpr::SubFloat(double cst) { - if (cst == 0.0) return this; - return new AffineExpr(this, 1.0, -cst); +std::shared_ptr LinearExpr::Sub(std::shared_ptr expr) { + std::vector> exprs; + exprs.push_back(shared_from_this()); + exprs.push_back(expr); + std::vector coeffs = {1.0, -1.0}; + return std::make_shared(exprs, coeffs, 0.0); } -LinearExpr* LinearExpr::RSubFloat(double cst) { - return new AffineExpr(this, -1.0, cst); +std::shared_ptr LinearExpr::SubFloat(double cst) { + if (cst == 0.0) return shared_from_this(); + return std::make_shared(shared_from_this(), 1.0, -cst); } -LinearExpr* LinearExpr::MulFloat(double cst) { - if (cst == 0.0) return new FixedValue(0.0); - if (cst == 1.0) return this; - return new AffineExpr(this, cst, 0.0); +std::shared_ptr LinearExpr::RSubFloat(double cst) { + return std::make_shared(shared_from_this(), -1.0, cst); } -LinearExpr* LinearExpr::Neg() { return new AffineExpr(this, -1, 0); } +std::shared_ptr LinearExpr::MulFloat(double cst) { + if (cst == 0.0) return std::make_shared(0.0); + if (cst == 1.0) return shared_from_this(); + return std::make_shared(shared_from_this(), cst, 0.0); +} + +std::shared_ptr LinearExpr::Neg() { + return std::make_shared(shared_from_this(), -1, 0); +} // Expression visitors. -void ExprVisitor::AddToProcess(const LinearExpr* expr, double coeff) { +void ExprVisitor::AddToProcess(std::shared_ptr expr, double coeff) { to_process_.push_back(std::make_pair(expr, coeff)); } @@ -842,11 +857,11 @@ void ExprVisitor::Clear() { offset_ = 0.0; } -void ExprFlattener::AddVarCoeff(const Variable* var, double coeff) { +void ExprFlattener::AddVarCoeff(std::shared_ptr var, double coeff) { canonical_terms_[var] += coeff; } -double ExprFlattener::Flatten(std::vector* vars, +double ExprFlattener::Flatten(std::vector>* vars, std::vector* coeffs) { while (!to_process_.empty()) { const auto [expr, coeff] = to_process_.back(); @@ -865,7 +880,7 @@ double ExprFlattener::Flatten(std::vector* vars, return offset_; } -void ExprEvaluator::AddVarCoeff(const Variable* var, double coeff) { +void ExprEvaluator::AddVarCoeff(std::shared_ptr var, double coeff) { offset_ += coeff * helper_->variable_value(var->index()); } @@ -879,20 +894,21 @@ double ExprEvaluator::Evaluate() { return offset_; } -FlatExpr::FlatExpr(const LinearExpr* expr) { +FlatExpr::FlatExpr(std::shared_ptr expr) { ExprFlattener lin; lin.AddToProcess(expr, 1.0); offset_ = lin.Flatten(&vars_, &coeffs_); } -FlatExpr::FlatExpr(const LinearExpr* pos, const LinearExpr* neg) { +FlatExpr::FlatExpr(std::shared_ptr pos, + std::shared_ptr neg) { ExprFlattener lin; lin.AddToProcess(pos, 1.0); lin.AddToProcess(neg, -1.0); offset_ = lin.Flatten(&vars_, &coeffs_); } -FlatExpr::FlatExpr(const std::vector& vars, +FlatExpr::FlatExpr(const std::vector>& vars, const std::vector& coeffs, double offset) : vars_(vars), coeffs_(coeffs), offset_(offset) {} @@ -901,13 +917,13 @@ FlatExpr::FlatExpr(double offset) : offset_(offset) {} std::vector FlatExpr::VarIndices() const { std::vector var_indices; var_indices.reserve(vars_.size()); - for (const Variable* var : vars_) { + for (const std::shared_ptr& var : vars_) { var_indices.push_back(var->index()); } return var_indices; } -void FlatExpr::Visit(ExprVisitor& lin, double c) const { +void FlatExpr::Visit(ExprVisitor& lin, double c) { for (int i = 0; i < vars_.size(); ++i) { lin.AddVarCoeff(vars_[i], coeffs_[i] * c); } @@ -966,9 +982,10 @@ std::string FlatExpr::ToString() const { std::string FlatExpr::DebugString() const { std::string s = absl::StrCat( "FlatExpr(", - absl::StrJoin(vars_, ", ", [](std::string* out, const Variable* expr) { - absl::StrAppend(out, expr->DebugString()); - })); + absl::StrJoin(vars_, ", ", + [](std::string* out, std::shared_ptr expr) { + absl::StrAppend(out, expr->DebugString()); + })); if (offset_ != 0.0) { absl::StrAppend(&s, ", offset=", offset_); } @@ -976,7 +993,7 @@ std::string FlatExpr::DebugString() const { return s; } -void FixedValue::Visit(ExprVisitor& lin, double c) const { +void FixedValue::Visit(ExprVisitor& lin, double c) { lin.AddConstant(value_ * c); } @@ -986,14 +1003,14 @@ std::string FixedValue::DebugString() const { return absl::StrCat("FixedValue(", value_, ")"); } -WeightedSumArray::WeightedSumArray(const std::vector& exprs, - const std::vector& coeffs, - double offset) +WeightedSumArray::WeightedSumArray( + const std::vector>& exprs, + const std::vector& coeffs, double offset) : exprs_(exprs.begin(), exprs.end()), coeffs_(coeffs.begin(), coeffs.end()), offset_(offset) {} -void WeightedSumArray::Visit(ExprVisitor& lin, double c) const { +void WeightedSumArray::Visit(ExprVisitor& lin, double c) { for (int i = 0; i < exprs_.size(); ++i) { lin.AddToProcess(exprs_[i], coeffs_[i] * c); } @@ -1047,22 +1064,44 @@ std::string WeightedSumArray::ToString() const { } std::string WeightedSumArray::DebugString() const { - return absl::StrCat("WeightedSumArray([", - absl::StrJoin(exprs_, ", ", - [](std::string* out, const LinearExpr* e) { - absl::StrAppend(out, e->DebugString()); - }), - "], [", absl::StrJoin(coeffs_, "], "), offset_, ")"); + return absl::StrCat( + "WeightedSumArray([", + absl::StrJoin(exprs_, ", ", + [](std::string* out, std::shared_ptr e) { + absl::StrAppend(out, e->DebugString()); + }), + "], [", absl::StrJoin(coeffs_, "], "), offset_, ")"); } -AffineExpr::AffineExpr(LinearExpr* expr, double coeff, double offset) +AffineExpr::AffineExpr(std::shared_ptr expr, double coeff, + double offset) : expr_(expr), coeff_(coeff), offset_(offset) {} -void AffineExpr::Visit(ExprVisitor& lin, double c) const { +void AffineExpr::Visit(ExprVisitor& lin, double c) { lin.AddToProcess(expr_, c * coeff_); lin.AddConstant(offset_ * c); } +std::shared_ptr AffineExpr::AddFloat(double cst) { + return LinearExpr::Affine(expr_, coeff_, offset_ + cst); +} + +std::shared_ptr AffineExpr::SubFloat(double cst) { + return LinearExpr::Affine(expr_, coeff_, offset_ - cst); +} + +std::shared_ptr AffineExpr::RSubFloat(double cst) { + return LinearExpr::Affine(expr_, -coeff_, cst - offset_); +} + +std::shared_ptr AffineExpr::MulFloat(double cst) { + return LinearExpr::Affine(expr_, coeff_ * cst, offset_ * cst); +} + +std::shared_ptr AffineExpr::Neg() { + return LinearExpr::Affine(expr_, -coeff_, -offset_); +} + std::string AffineExpr::ToString() const { std::string s = "("; if (coeff_ == 1.0) { @@ -1085,36 +1124,41 @@ std::string AffineExpr::DebugString() const { return absl::StrCat("AffineExpr(expr=", expr_->DebugString(), ", coeff=", coeff_, ", offset=", offset_, ")"); } -BoundedLinearExpression* LinearExpr::Eq(LinearExpr* rhs) { - return new BoundedLinearExpression(this, rhs, 0.0, 0.0); +std::shared_ptr LinearExpr::Eq( + std::shared_ptr rhs) { + return std::make_shared(shared_from_this(), rhs, 0.0, + 0.0); } -BoundedLinearExpression* LinearExpr::EqCst(double rhs) { - return new BoundedLinearExpression(this, rhs, rhs); +std::shared_ptr LinearExpr::EqCst(double rhs) { + return std::make_shared(shared_from_this(), rhs, + rhs); } -BoundedLinearExpression* LinearExpr::Le(LinearExpr* rhs) { - return new BoundedLinearExpression( - this, rhs, -std::numeric_limits::infinity(), 0.0); +std::shared_ptr LinearExpr::Le( + std::shared_ptr rhs) { + return std::make_shared( + shared_from_this(), rhs, -std::numeric_limits::infinity(), 0.0); } -BoundedLinearExpression* LinearExpr::LeCst(double rhs) { - return new BoundedLinearExpression( - this, -std::numeric_limits::infinity(), rhs); +std::shared_ptr LinearExpr::LeCst(double rhs) { + return std::make_shared( + shared_from_this(), -std::numeric_limits::infinity(), rhs); } -BoundedLinearExpression* LinearExpr::Ge(LinearExpr* rhs) { - return new BoundedLinearExpression(this, rhs, 0.0, - std::numeric_limits::infinity()); +std::shared_ptr LinearExpr::Ge( + std::shared_ptr rhs) { + return std::make_shared( + shared_from_this(), rhs, 0.0, std::numeric_limits::infinity()); } -BoundedLinearExpression* LinearExpr::GeCst(double rhs) { - return new BoundedLinearExpression(this, rhs, - std::numeric_limits::infinity()); +std::shared_ptr LinearExpr::GeCst(double rhs) { + return std::make_shared( + shared_from_this(), rhs, std::numeric_limits::infinity()); } -bool VariableComparator::operator()(const Variable* lhs, - const Variable* rhs) const { +bool VariableComparator::operator()(std::shared_ptr lhs, + std::shared_ptr rhs) const { return lhs->index() < rhs->index(); } @@ -1211,9 +1255,8 @@ void Variable::SetObjectiveCoefficient(double coeff) { helper_->SetVarObjectiveCoefficient(index_, coeff); } -BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* expr, - double lower_bound, - double upper_bound) { +BoundedLinearExpression::BoundedLinearExpression( + std::shared_ptr expr, double lower_bound, double upper_bound) { FlatExpr flat_expr(expr); vars_ = flat_expr.vars(); coeffs_ = flat_expr.coeffs(); @@ -1221,10 +1264,9 @@ BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* expr, upper_bound_ = upper_bound - flat_expr.offset(); } -BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* pos, - const LinearExpr* neg, - double lower_bound, - double upper_bound) { +BoundedLinearExpression::BoundedLinearExpression( + std::shared_ptr pos, std::shared_ptr neg, + double lower_bound, double upper_bound) { FlatExpr flat_expr(pos, neg); vars_ = flat_expr.vars(); coeffs_ = flat_expr.coeffs(); @@ -1232,9 +1274,9 @@ BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* pos, upper_bound_ = upper_bound - flat_expr.offset(); } -BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* expr, - int64_t lower_bound, - int64_t upper_bound) { +BoundedLinearExpression::BoundedLinearExpression( + std::shared_ptr expr, int64_t lower_bound, + int64_t upper_bound) { FlatExpr flat_expr(expr); vars_ = flat_expr.vars(); coeffs_ = flat_expr.coeffs(); @@ -1242,10 +1284,9 @@ BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* expr, upper_bound_ = upper_bound - flat_expr.offset(); } -BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* pos, - const LinearExpr* neg, - int64_t lower_bound, - int64_t upper_bound) { +BoundedLinearExpression::BoundedLinearExpression( + std::shared_ptr pos, std::shared_ptr neg, + int64_t lower_bound, int64_t upper_bound) { FlatExpr flat_expr(pos, neg); vars_ = flat_expr.vars(); coeffs_ = flat_expr.coeffs(); @@ -1255,7 +1296,8 @@ BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* pos, double BoundedLinearExpression::lower_bound() const { return lower_bound_; } double BoundedLinearExpression::upper_bound() const { return upper_bound_; } -const std::vector& BoundedLinearExpression::vars() const { +const std::vector>& BoundedLinearExpression::vars() + const { return vars_; } const std::vector& BoundedLinearExpression::coeffs() const { @@ -1302,13 +1344,13 @@ std::string BoundedLinearExpression::ToString() const { } if (lower_bound_ == upper_bound_) { return absl::StrCat(s, " == ", lower_bound_); - } else if (lower_bound_ == std::numeric_limits::min()) { - if (upper_bound_ == std::numeric_limits::max()) { - return absl::StrCat("True (unbounded expr ", s, ")"); + } else if (lower_bound_ == -std::numeric_limits::infinity()) { + if (upper_bound_ == std::numeric_limits::infinity()) { + return absl::StrCat("-inf <= ", s, " <= inf"); } else { return absl::StrCat(s, " <= ", upper_bound_); } - } else if (upper_bound_ == std::numeric_limits::max()) { + } else if (upper_bound_ == std::numeric_limits::infinity()) { return absl::StrCat(s, " >= ", lower_bound_); } else { return absl::StrCat(lower_bound_, " <= ", s, " <= ", upper_bound_); @@ -1316,14 +1358,14 @@ std::string BoundedLinearExpression::ToString() const { } std::string BoundedLinearExpression::DebugString() const { - return absl::StrCat("BoundedLinearExpression(vars=[", - absl::StrJoin(vars_, ", ", - [](std::string* out, const Variable* var) { - absl::StrAppend(out, var->DebugString()); - }), - "], coeffs=[", absl::StrJoin(coeffs_, ", "), - "], lower_bound=", lower_bound_, - ", upper_bound=", upper_bound_, ")"); + return absl::StrCat( + "BoundedLinearExpression(vars=[", + absl::StrJoin(vars_, ", ", + [](std::string* out, std::shared_ptr var) { + absl::StrAppend(out, var->DebugString()); + }), + "], coeffs=[", absl::StrJoin(coeffs_, ", "), + "], lower_bound=", lower_bound_, ", upper_bound=", upper_bound_, ")"); } bool BoundedLinearExpression::CastToBool(bool* result) const { diff --git a/ortools/linear_solver/wrappers/model_builder_helper.h b/ortools/linear_solver/wrappers/model_builder_helper.h index 5b42e97fb8..d7abe63790 100644 --- a/ortools/linear_solver/wrappers/model_builder_helper.h +++ b/ortools/linear_solver/wrappers/model_builder_helper.h @@ -14,9 +14,11 @@ #ifndef OR_TOOLS_LINEAR_SOLVER_WRAPPERS_MODEL_BUILDER_HELPER_H_ #define OR_TOOLS_LINEAR_SOLVER_WRAPPERS_MODEL_BUILDER_HELPER_H_ +#include #include #include #include +#include #include #include #include @@ -43,70 +45,75 @@ class ModelBuilderHelper; class ModelSolverHelper; class Variable; -// A linear expression that can be either integer or floating point. -class LinearExpr { +// A linear expression that containing variables and constants. +class LinearExpr : public std::enable_shared_from_this { public: virtual ~LinearExpr() = default; - virtual void Visit(ExprVisitor& /*lin*/, double /*c*/) const = 0; + virtual void Visit(ExprVisitor& /*lin*/, double /*c*/) = 0; virtual std::string ToString() const = 0; virtual std::string DebugString() const = 0; - static LinearExpr* Term(LinearExpr* expr, double coeff); - static LinearExpr* Affine(LinearExpr* expr, double coeff, double constant); - static LinearExpr* AffineCst(double value, double coeff, double constant); - static LinearExpr* Constant(double value); + static std::shared_ptr Term(std::shared_ptr expr, + double coeff); + static std::shared_ptr Affine(std::shared_ptr expr, + double coeff, double constant); + static std::shared_ptr AffineCst(double value, double coeff, + double constant); + static std::shared_ptr Constant(double value); - LinearExpr* Add(LinearExpr* expr); - LinearExpr* AddFloat(double cst); - LinearExpr* Sub(LinearExpr* expr); - LinearExpr* SubFloat(double cst); - LinearExpr* RSubFloat(double cst); - LinearExpr* MulFloat(double cst); - LinearExpr* Neg(); + std::shared_ptr Add(std::shared_ptr expr); + std::shared_ptr AddFloat(double cst); + std::shared_ptr Sub(std::shared_ptr expr); + std::shared_ptr SubFloat(double cst); + std::shared_ptr RSubFloat(double cst); + std::shared_ptr MulFloat(double cst); + std::shared_ptr Neg(); - BoundedLinearExpression* Eq(LinearExpr* rhs); - BoundedLinearExpression* EqCst(double rhs); - BoundedLinearExpression* Ge(LinearExpr* rhs); - BoundedLinearExpression* GeCst(double rhs); - BoundedLinearExpression* Le(LinearExpr* rhs); - BoundedLinearExpression* LeCst(double rhs); + std::shared_ptr Eq(std::shared_ptr rhs); + std::shared_ptr EqCst(double rhs); + std::shared_ptr Ge(std::shared_ptr rhs); + std::shared_ptr GeCst(double rhs); + std::shared_ptr Le(std::shared_ptr rhs); + std::shared_ptr LeCst(double rhs); }; // Compare the indices of variables. struct VariableComparator { - bool operator()(const Variable* lhs, const Variable* rhs) const; + bool operator()(std::shared_ptr lhs, + std::shared_ptr rhs) const; }; // A visitor class to parse a floating point linear expression. class ExprVisitor { public: virtual ~ExprVisitor() = default; - void AddToProcess(const LinearExpr* expr, double coeff); + void AddToProcess(std::shared_ptr expr, double coeff); void AddConstant(double constant); - virtual void AddVarCoeff(const Variable* var, double coeff) = 0; + virtual void AddVarCoeff(std::shared_ptr var, double coeff) = 0; void Clear(); protected: - std::vector> to_process_; + std::vector, double>> to_process_; double offset_ = 0; }; class ExprFlattener : public ExprVisitor { public: ~ExprFlattener() override = default; - void AddVarCoeff(const Variable* var, double coeff) override; - double Flatten(std::vector* vars, + void AddVarCoeff(std::shared_ptr var, double coeff) override; + double Flatten(std::vector>* vars, std::vector* coeffs); private: - absl::btree_map canonical_terms_; + absl::btree_map, double, VariableComparator> + canonical_terms_; }; class ExprEvaluator : public ExprVisitor { public: explicit ExprEvaluator(ModelSolverHelper* helper) : helper_(helper) {} ~ExprEvaluator() override = default; - void AddVarCoeff(const Variable* var, double coeff) override; + void AddVarCoeff(std::shared_ptr var, double coeff) override; double Evaluate(); private: @@ -116,23 +123,23 @@ class ExprEvaluator : public ExprVisitor { // A flat linear expression sum(vars[i] * coeffs[i]) + offset class FlatExpr : public LinearExpr { public: - explicit FlatExpr(const LinearExpr* expr); + explicit FlatExpr(std::shared_ptr expr); // Flatten pos - neg. - FlatExpr(const LinearExpr* pos, const LinearExpr* neg); - FlatExpr(const std::vector&, const std::vector&, - double); + FlatExpr(std::shared_ptr pos, std::shared_ptr neg); + FlatExpr(const std::vector>&, + const std::vector&, double); explicit FlatExpr(double offset); - const std::vector& vars() const { return vars_; } + const std::vector>& vars() const { return vars_; } std::vector VarIndices() const; const std::vector& coeffs() const { return coeffs_; } double offset() const { return offset_; } - void Visit(ExprVisitor& lin, double c) const override; + void Visit(ExprVisitor& lin, double c) override; std::string ToString() const override; std::string DebugString() const override; private: - std::vector vars_; + std::vector> vars_; std::vector coeffs_; double offset_; }; @@ -141,11 +148,12 @@ class FlatExpr : public LinearExpr { // double offsets. class SumArray : public LinearExpr { public: - explicit SumArray(const std::vector& exprs, double offset) - : exprs_(exprs.begin(), exprs.end()), offset_(offset) {} + explicit SumArray(std::vector> exprs, + double offset) + : exprs_(std::move(exprs)), offset_(offset) {} ~SumArray() override = default; - void Visit(ExprVisitor& lin, double c) const override { + void Visit(ExprVisitor& lin, double c) override { for (int i = 0; i < exprs_.size(); ++i) { lin.AddToProcess(exprs_[i], c); } @@ -181,9 +189,10 @@ class SumArray : public LinearExpr { std::string DebugString() const override { std::string s = absl::StrCat( "SumArray(", - absl::StrJoin(exprs_, ", ", [](std::string* out, LinearExpr* expr) { - absl::StrAppend(out, expr->DebugString()); - })); + absl::StrJoin(exprs_, ", ", + [](std::string* out, std::shared_ptr expr) { + absl::StrAppend(out, expr->DebugString()); + })); if (offset_ != 0.0) { absl::StrAppend(&s, ", offset=", offset_); } @@ -191,24 +200,29 @@ class SumArray : public LinearExpr { return s; } + void AddInPlace(std::shared_ptr expr) { exprs_.push_back(expr); } + void AddFloatInPlace(double cst) { offset_ += cst; } + int num_exprs() const { return exprs_.size(); } + double offset() const { return offset_; } + private: - const absl::FixedArray exprs_; - const double offset_; + std::vector> exprs_; + double offset_; }; // A class to hold a weighted sum of floating point linear expressions. class WeightedSumArray : public LinearExpr { public: - WeightedSumArray(const std::vector& exprs, + WeightedSumArray(const std::vector>& exprs, const std::vector& coeffs, double offset); ~WeightedSumArray() override = default; - void Visit(ExprVisitor& lin, double c) const override; + void Visit(ExprVisitor& lin, double c) override; std::string ToString() const override; std::string DebugString() const override; private: - const absl::FixedArray exprs_; + const absl::FixedArray, 2> exprs_; const absl::FixedArray coeffs_; double offset_; }; @@ -216,20 +230,26 @@ class WeightedSumArray : public LinearExpr { // A class to hold linear_expr * a = b. class AffineExpr : public LinearExpr { public: - AffineExpr(LinearExpr* expr, double coeff, double offset); + AffineExpr(std::shared_ptr expr, double coeff, double offset); ~AffineExpr() override = default; - void Visit(ExprVisitor& lin, double c) const override; + void Visit(ExprVisitor& lin, double c) override; std::string ToString() const override; std::string DebugString() const override; - LinearExpr* expression() const { return expr_; } + std::shared_ptr expression() const { return expr_; } double coefficient() const { return coeff_; } double offset() const { return offset_; } + std::shared_ptr AddFloat(double cst); + std::shared_ptr SubFloat(double cst); + std::shared_ptr RSubFloat(double cst); + std::shared_ptr MulFloat(double cst); + std::shared_ptr Neg(); + private: - LinearExpr* expr_; + std::shared_ptr expr_; double coeff_; double offset_; }; @@ -240,7 +260,7 @@ class FixedValue : public LinearExpr { explicit FixedValue(double value) : value_(value) {} ~FixedValue() override = default; - void Visit(ExprVisitor& lin, double c) const override; + void Visit(ExprVisitor& lin, double c) override; std::string ToString() const override; std::string DebugString() const override; @@ -275,8 +295,10 @@ class Variable : public LinearExpr { double objective_coefficient() const; void SetObjectiveCoefficient(double coeff); - void Visit(ExprVisitor& lin, double c) const override { - lin.AddVarCoeff(this, c); + void Visit(ExprVisitor& lin, double c) override { + std::shared_ptr var = + std::static_pointer_cast(shared_from_this()); + lin.AddVarCoeff(var, c); } std::string ToString() const override; @@ -291,34 +313,36 @@ class Variable : public LinearExpr { }; template -H AbslHashValue(H h, const Variable* i) { +H AbslHashValue(H h, std::shared_ptr i) { return H::combine(std::move(h), i->index()); } // A class to hold a linear expression with bounds. class BoundedLinearExpression { public: - BoundedLinearExpression(const LinearExpr* expr, double lower_bound, + BoundedLinearExpression(std::shared_ptr expr, double lower_bound, double upper_bound); - BoundedLinearExpression(const LinearExpr* pos, const LinearExpr* neg, - double lower_bound, double upper_bound); - BoundedLinearExpression(const LinearExpr* expr, int64_t lower_bound, + BoundedLinearExpression(std::shared_ptr pos, + std::shared_ptr neg, double lower_bound, + double upper_bound); + BoundedLinearExpression(std::shared_ptr expr, int64_t lower_bound, + int64_t upper_bound); + BoundedLinearExpression(std::shared_ptr pos, + std::shared_ptr neg, int64_t lower_bound, int64_t upper_bound); - BoundedLinearExpression(const LinearExpr* pos, const LinearExpr* neg, - int64_t lower_bound, int64_t upper_bound); ~BoundedLinearExpression() = default; double lower_bound() const; double upper_bound() const; - const std::vector& vars() const; + const std::vector>& vars() const; const std::vector& coeffs() const; std::string ToString() const; std::string DebugString() const; bool CastToBool(bool* result) const; private: - std::vector vars_; + std::vector> vars_; std::vector coeffs_; double lower_bound_; double upper_bound_; @@ -485,7 +509,7 @@ class ModelSolverHelper { double objective_value() const; double best_objective_bound() const; double variable_value(int var_index) const; - double expression_value(LinearExpr* expr) const; + double expression_value(std::shared_ptr expr) const; double reduced_cost(int var_index) const; double dual_value(int ct_index) const; double activity(int ct_index);