diff --git a/ortools/sat/python/BUILD.bazel b/ortools/sat/python/BUILD.bazel index 72d07918f8..8d8da9a951 100644 --- a/ortools/sat/python/BUILD.bazel +++ b/ortools/sat/python/BUILD.bazel @@ -39,6 +39,7 @@ pybind_extension( deps = [ ":linear_expr", "//ortools/sat:cp_model_cc_proto", + "//ortools/sat:cp_model_utils", "//ortools/sat:sat_parameters_cc_proto", "//ortools/sat:swig_helper", "@com_google_absl//absl/strings", diff --git a/ortools/sat/python/cp_model_test.py b/ortools/sat/python/cp_model_test.py index 4001a88d54..61a03f9076 100644 --- a/ortools/sat/python/cp_model_test.py +++ b/ortools/sat/python/cp_model_test.py @@ -1167,10 +1167,10 @@ def testRepr(self) -> None: z = model.new_int_var(0, 3, "z") self.assertEqual(repr(x), "x(0..4)") self.assertEqual(repr(x * 2), "IntAffine(expr=x(0..4), coeff=2, offset=0)") - self.assertEqual(repr(x + y), "BinaryAdd(x(0..4), y(0..3))") + self.assertEqual(repr(x + y), "SumArray(x(0..4), y(0..3))") self.assertEqual( repr(cp_model.LinearExpr.sum([x, y, z])), - "IntSum(x(0..4), y(0..3), z(0..3), 0)", + "SumArray(x(0..4), y(0..3), z(0..3))", ) self.assertEqual( repr(cp_model.LinearExpr.weighted_sum([x, y, 2], [1, 2, 3])), diff --git a/ortools/sat/python/linear_expr.cc b/ortools/sat/python/linear_expr.cc index 11f4c417a4..60bac8275f 100644 --- a/ortools/sat/python/linear_expr.cc +++ b/ortools/sat/python/linear_expr.cc @@ -40,7 +40,7 @@ LinearExpr* LinearExpr::Sum(const std::vector& exprs) { } else if (exprs.size() == 1) { return exprs[0]; } else { - return new IntSum(exprs, 0); + return new SumArray(exprs); } } @@ -70,7 +70,7 @@ LinearExpr* LinearExpr::MixedSum(const std::vector& exprs) { } else if (lin_exprs.size() == 1) { return new IntAffine(lin_exprs[0], 1, int_offset); } else { - return new IntSum(lin_exprs, int_offset); + return new SumArray(lin_exprs, int_offset); } } else { // General floating point case. double_offset += static_cast(int_offset); @@ -79,7 +79,7 @@ LinearExpr* LinearExpr::MixedSum(const std::vector& exprs) { } else if (lin_exprs.size() == 1) { return new FloatAffine(lin_exprs[0], 1.0, double_offset); } else { - return new FloatSum(lin_exprs, double_offset); + return new SumArray(lin_exprs, 0, double_offset); } } } @@ -191,7 +191,7 @@ LinearExpr* LinearExpr::ConstantDouble(double value) { } LinearExpr* LinearExpr::Add(LinearExpr* expr) { - return new BinaryAdd(this, expr); + return new SumArray({this, expr}); } LinearExpr* LinearExpr::AddInt(int64_t cst) { diff --git a/ortools/sat/python/linear_expr.h b/ortools/sat/python/linear_expr.h index 60af496a9f..51f051e068 100644 --- a/ortools/sat/python/linear_expr.h +++ b/ortools/sat/python/linear_expr.h @@ -173,57 +173,44 @@ class CanonicalIntExpression { bool ok_; }; -class BinaryAdd : public LinearExpr { +// A class to hold a sum of linear expressions, and optional integer and +// double offsets. +class SumArray : public LinearExpr { public: - BinaryAdd(LinearExpr* lhs, LinearExpr* rhs) : lhs_(lhs), rhs_(rhs) {} - ~BinaryAdd() override = default; - - void VisitAsFloat(FloatExprVisitor* lin, double c) override { - lin->AddToProcess(lhs_, c); - lin->AddToProcess(rhs_, c); - } + explicit SumArray(const std::vector& exprs, + int64_t int_offset = 0, double double_offset = 0.0) + : exprs_(exprs.begin(), exprs.end()), + int_offset_(int_offset), + double_offset_(double_offset) {} + ~SumArray() override = default; bool VisitAsInt(IntExprVisitor* lin, int64_t c) override { - lin->AddToProcess(lhs_, c); - lin->AddToProcess(rhs_, c); + if (double_offset_ != 0.0) return false; + for (int i = 0; i < exprs_.size(); ++i) { + lin->AddToProcess(exprs_[i], c); + } + lin->AddConstant(int_offset_ * c); return true; } - std::string ToString() const override { - return absl::StrCat("(", lhs_->ToString(), " + ", rhs_->ToString(), ")"); - } - - std::string DebugString() const override { - return absl::StrCat("BinaryAdd(", lhs_->DebugString(), ", ", - rhs_->DebugString(), ")"); - } - - private: - LinearExpr* lhs_; - LinearExpr* rhs_; -}; - -// A class to hold a sum of floating point linear expressions. -class FloatSum : public LinearExpr { - public: - FloatSum(const std::vector& exprs, double offset) - : exprs_(exprs.begin(), exprs.end()), offset_(offset) {} - ~FloatSum() override = default; - - bool VisitAsInt(IntExprVisitor* /*lin*/, int64_t /*c*/) override { - return false; - } - void VisitAsFloat(FloatExprVisitor* lin, double c) override { for (int i = 0; i < exprs_.size(); ++i) { lin->AddToProcess(exprs_[i], c); } - lin->AddConstant(offset_ * c); + if (int_offset_ != 0) { + lin->AddConstant(int_offset_ * c); + } else if (double_offset_ != 0.0) { + lin->AddConstant(double_offset_ * c); + } } std::string ToString() const override { if (exprs_.empty()) { - return absl::StrCat(offset_); + if (double_offset_ != 0.0) { + return absl::StrCat(double_offset_); + } else { + return absl::StrCat(int_offset_); + } } std::string s = "("; for (int i = 0; i < exprs_.size(); ++i) { @@ -232,11 +219,18 @@ class FloatSum : public LinearExpr { } absl::StrAppend(&s, exprs_[i]->ToString()); } - if (offset_ != 0.0) { - if (offset_ > 0.0) { - absl::StrAppend(&s, " + ", offset_); + if (double_offset_ != 0.0) { + if (double_offset_ > 0.0) { + absl::StrAppend(&s, " + ", double_offset_); } else { - absl::StrAppend(&s, " - ", -offset_); + absl::StrAppend(&s, " - ", -double_offset_); + } + } + if (int_offset_ != 0) { + if (int_offset_ > 0) { + absl::StrAppend(&s, " + ", int_offset_); + } else { + absl::StrAppend(&s, " - ", -int_offset_); } } absl::StrAppend(&s, ")"); @@ -244,77 +238,25 @@ class FloatSum : public LinearExpr { } std::string DebugString() const override { - return absl::StrCat("FloatSum(", - absl::StrJoin(exprs_, ", ", - [](std::string* out, LinearExpr* expr) { - absl::StrAppend(out, - expr->DebugString()); - }), - ", ", offset_, ")"); - } - - private: - const absl::FixedArray exprs_; - double offset_; -}; - -// A class to hold a sum of integer linear expressions. -class IntSum : public LinearExpr { - public: - IntSum(const std::vector& exprs, int64_t offset) - : exprs_(exprs.begin(), exprs.end()), offset_(offset) {} - ~IntSum() override = default; - - bool VisitAsInt(IntExprVisitor* lin, int64_t c) override { - for (int i = 0; i < exprs_.size(); ++i) { - lin->AddToProcess(exprs_[i], c); - } - lin->AddConstant(offset_ * c); - return true; - } - - void VisitAsFloat(FloatExprVisitor* lin, double c) override { - for (int i = 0; i < exprs_.size(); ++i) { - lin->AddToProcess(exprs_[i], c); - } - lin->AddConstant(offset_ * c); - } - - std::string ToString() const override { - if (exprs_.empty()) { - return absl::StrCat(offset_); + std::string s = absl::StrCat( + "SumArray(", + absl::StrJoin(exprs_, ", ", [](std::string* out, LinearExpr* expr) { + absl::StrAppend(out, expr->DebugString()); + })); + if (int_offset_ != 0) { + absl::StrAppend(&s, ", int_offset=", int_offset_); } - std::string s = "("; - for (int i = 0; i < exprs_.size(); ++i) { - if (i > 0) { - absl::StrAppend(&s, " + "); - } - absl::StrAppend(&s, exprs_[i]->ToString()); - } - if (offset_ != 0) { - if (offset_ > 0) { - absl::StrAppend(&s, " + ", offset_); - } else { - absl::StrAppend(&s, " - ", -offset_); - } + if (double_offset_ != 0.0) { + absl::StrAppend(&s, ", double_offset=", double_offset_); } absl::StrAppend(&s, ")"); return s; } - std::string DebugString() const override { - return absl::StrCat("IntSum(", - absl::StrJoin(exprs_, ", ", - [](std::string* out, LinearExpr* expr) { - absl::StrAppend(out, - expr->DebugString()); - }), - ", ", offset_, ")"); - } - private: const absl::FixedArray exprs_; - int64_t offset_; + const int64_t int_offset_; + const double double_offset_; }; // A class to hold a weighted sum of floating point linear expressions. diff --git a/ortools/sat/python/swig_helper.cc b/ortools/sat/python/swig_helper.cc index 5c15800861..7534499bf6 100644 --- a/ortools/sat/python/swig_helper.cc +++ b/ortools/sat/python/swig_helper.cc @@ -23,6 +23,7 @@ #include "absl/strings/str_cat.h" #include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_utils.h" #include "ortools/sat/python/linear_expr.h" #include "ortools/util/sorted_interval_list.h" #include "pybind11/cast.h" @@ -40,6 +41,13 @@ namespace python { using ::py::arg; +void throw_error(PyObject* py_exception, const std::string& message) { + PyErr_SetString(py_exception, message.c_str()); + throw py::error_already_set(); +} + +// A trampoline class to override the OnSolutionCallback method to acquire the +// GIL. class PySolutionCallback : public SolutionCallback { public: using SolutionCallback::SolutionCallback; /* Inherit constructors */ @@ -56,11 +64,6 @@ class PySolutionCallback : public SolutionCallback { } }; -void throw_error(PyObject* py_exception, const std::string& message) { - PyErr_SetString(py_exception, message.c_str()); - throw py::error_already_set(); -} - // A trampoline class to override the __str__ and __repr__ methods. class PyBaseIntVar : public BaseIntVar { public: @@ -97,7 +100,7 @@ class ResponseWrapper { if (index >= 0) { return response_.solution(index) != 0; } else { - return response_.solution(-index - 1) == 0; + return response_.solution(NegatedRef(index)) == 0; } } @@ -191,10 +194,68 @@ const char* kLinearExprClassDoc = R"doc( model.add(cp_model.LinearExpr.weighted_sum(expressions, coefficients) >= 0) ```)doc"; +const char* kLiteralClassDoc = R"doc( + Holds a Boolean literal. + + A literal is a Boolean variable or its negation. + + Literals are used in CP-SAT models in constraints and in the + objective: + + * You can define literal as in: + + ``` + b1 = model.new_bool_var() + b2 = model.new_bool_var() + # Simple Boolean constraint. + model.add_bool_or(b1, b2.negated()) + # We can use the ~ operator to negate a literal. + model.add_bool_or(b1, ~b2) + # Enforcement literals must be literals. + x = model.new_int_var(0, 10, 'x') + model.add(x == 5).only_enforced_if(~b1) + ``` + + * Literals can be used directly in linear constraints or in the objective: + + ``` + model.minimize(b1 + 2 * ~b2) + ```)doc"; + +// Checks that the result is not null and throws an error if it is. +BoundedLinearExpression* CheckBoundedLinearExpression( + BoundedLinearExpression* result, LinearExpr* lhs, + LinearExpr* rhs = nullptr) { + if (result == nullptr) { + if (rhs == nullptr) { + throw_error(PyExc_TypeError, + absl::StrCat("Linear constraints only accept integer values " + "and coefficients: ", + lhs->DebugString())); + } else { + throw_error( + PyExc_TypeError, + absl::StrCat("Linear constraints only accept integer values " + "and coefficients: ", + lhs->DebugString(), " and ", rhs->DebugString())); + } + } + return result; +} + +void RaiseIfNone(LinearExpr* expr) { + if (expr == nullptr) { + throw_error(PyExc_TypeError, + "Linear constraints do not accept None as argument."); + } +} + PYBIND11_MODULE(swig_helper, m) { pybind11_protobuf::ImportNativeProtoCasters(); py::module::import("ortools.util.python.sorted_interval_list"); + // We keep the CamelCase name for the SolutionCallback class to be compatible + // with the pre PEP8 python code. py::class_(m, "SolutionCallback") .def(py::init<>()) .def("OnSolutionCallback", &SolutionCallback::OnSolutionCallback) @@ -234,17 +295,12 @@ PYBIND11_MODULE(swig_helper, m) { .def( "BooleanValue", [](const SolutionCallback& callback, Literal* lit) { - const int index = lit->index(); - if (index >= 0) { - return callback.Response().solution(index) != 0; - } else { - return callback.Response().solution(-index - 1) == 0; - } + return callback.SolutionBooleanValue(lit->index()); }, - "Returns the boolean value of a literal after solve.") + "Returns the Boolean value of a literal after solve.") .def( "BooleanValue", [](const SolutionCallback&, bool lit) { return lit; }, - "Returns the boolean value of a literal after solve."); + "Returns the Boolean value of a literal after solve."); py::class_(m, "ResponseWrapper") .def(py::init()) @@ -356,6 +412,7 @@ PYBIND11_MODULE(swig_helper, m) { .def_static("constant", &LinearExpr::ConstantDouble, arg("value"), "Returns a constant linear expression.", py::return_value_policy::automatic) + // Pre PEP8 compatibility layer. .def_static("Sum", &LinearExpr::Sum, arg("exprs"), py::return_value_policy::automatic, py::keep_alive<0, 1>()) .def_static("Sum", &LinearExpr::MixedSum, arg("exprs"), @@ -372,9 +429,13 @@ PYBIND11_MODULE(swig_helper, m) { .def_static("Term", &LinearExpr::TermDouble, arg("expr").none(false), arg("coeff"), "Returns expr * coeff.", py::return_value_policy::automatic, py::keep_alive<0, 1>()) + // Methods. .def("__str__", &LinearExpr::ToString) .def("__repr__", &LinearExpr::DebugString) .def("is_integer", &LinearExpr::IsInteger) + // Operators. + // Note that we keep the 3 APIS (expr, int, double) instead of using an + // ExprOrValue 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>()) @@ -409,294 +470,154 @@ PYBIND11_MODULE(swig_helper, m) { py::keep_alive<0, 1>()) .def( "__eq__", - [](LinearExpr* expr, LinearExpr* rhs) { - if (rhs == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints do not accept None as argument."); - } - BoundedLinearExpression* result = expr->Eq(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + [](LinearExpr* lhs, LinearExpr* rhs) { + RaiseIfNone(rhs); + return CheckBoundedLinearExpression(lhs->Eq(rhs), lhs, rhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>(), py::keep_alive<0, 2>()) .def( "__eq__", - [](LinearExpr* expr, int64_t rhs) { + [](LinearExpr* lhs, int64_t rhs) { if (rhs == std::numeric_limits::max() || rhs == std::numeric_limits::min()) { throw_error(PyExc_ValueError, "== INT_MIN or INT_MAX is not supported"); } - BoundedLinearExpression* result = expr->EqCst(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + return CheckBoundedLinearExpression(lhs->EqCst(rhs), lhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>()) .def( "__ne__", - [](LinearExpr* expr, LinearExpr* rhs) { - if (rhs == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints do not accept None as argument."); - } - BoundedLinearExpression* result = expr->Ne(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + [](LinearExpr* lhs, LinearExpr* rhs) { + RaiseIfNone(rhs); + return CheckBoundedLinearExpression(lhs->Ne(rhs), lhs, rhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>(), py::keep_alive<0, 2>()) .def( "__ne__", - [](LinearExpr* expr, int64_t rhs) { - BoundedLinearExpression* result = expr->NeCst(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + [](LinearExpr* lhs, int64_t rhs) { + return CheckBoundedLinearExpression(lhs->NeCst(rhs), lhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>()) .def( "__le__", - [](LinearExpr* expr, LinearExpr* rhs) { - if (rhs == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints do not accept None as argument."); - } - BoundedLinearExpression* result = expr->Le(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + [](LinearExpr* lhs, LinearExpr* rhs) { + RaiseIfNone(rhs); + return CheckBoundedLinearExpression(lhs->Le(rhs), lhs, rhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>(), py::keep_alive<0, 2>()) .def( "__le__", - [](LinearExpr* expr, int64_t rhs) { + [](LinearExpr* lhs, int64_t rhs) { if (rhs == std::numeric_limits::min()) { throw_error(PyExc_ArithmeticError, "<= INT_MIN is not supported"); } - BoundedLinearExpression* result = expr->LeCst(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + return CheckBoundedLinearExpression(lhs->LeCst(rhs), lhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>()) .def( "__lt__", - [](LinearExpr* expr, LinearExpr* rhs) { - if (rhs == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints do not accept None as argument."); - } - BoundedLinearExpression* result = expr->Lt(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + [](LinearExpr* lhs, LinearExpr* rhs) { + RaiseIfNone(rhs); + return CheckBoundedLinearExpression(lhs->Lt(rhs), lhs, rhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>(), py::keep_alive<0, 2>()) .def( "__lt__", - [](LinearExpr* expr, int64_t rhs) { + [](LinearExpr* lhs, int64_t rhs) { if (rhs == std::numeric_limits::min()) { throw_error(PyExc_ArithmeticError, "< INT_MIN is not supported"); } - BoundedLinearExpression* result = expr->LtCst(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + return CheckBoundedLinearExpression(lhs->LtCst(rhs), lhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>()) .def( "__ge__", - [](LinearExpr* expr, LinearExpr* rhs) { - if (rhs == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints do not accept None as argument."); - } - BoundedLinearExpression* result = expr->Ge(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + [](LinearExpr* lhs, LinearExpr* rhs) { + RaiseIfNone(rhs); + return CheckBoundedLinearExpression(lhs->Ge(rhs), lhs, rhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>(), py::keep_alive<0, 2>()) .def( "__ge__", - [](LinearExpr* expr, int64_t rhs) { + [](LinearExpr* lhs, int64_t rhs) { if (rhs == std::numeric_limits::max()) { throw_error(PyExc_ArithmeticError, ">= INT_MAX is not supported"); } - BoundedLinearExpression* result = expr->GeCst(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + return CheckBoundedLinearExpression(lhs->GeCst(rhs), lhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>()) .def( "__gt__", - [](LinearExpr* expr, LinearExpr* rhs) { - if (rhs == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints do not accept None as argument."); - } - BoundedLinearExpression* result = expr->Gt(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + [](LinearExpr* lhs, LinearExpr* rhs) { + RaiseIfNone(rhs); + return CheckBoundedLinearExpression(lhs->Gt(rhs), lhs, rhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>(), py::keep_alive<0, 2>()) .def( "__gt__", - [](LinearExpr* expr, int64_t rhs) { + [](LinearExpr* lhs, int64_t rhs) { if (rhs == std::numeric_limits::max()) { throw_error(PyExc_ArithmeticError, "> INT_MAX is not supported"); } - BoundedLinearExpression* result = expr->GtCst(rhs); - if (result == nullptr) { - throw_error(PyExc_TypeError, - "Linear constraints only accept integer values " - "and coefficients."); - } - return result; + return CheckBoundedLinearExpression(lhs->GtCst(rhs), lhs); }, py::return_value_policy::automatic, py::keep_alive<0, 1>()) + // Disable other operators as they are not supported. .def("__div__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { - throw_error(PyExc_NotImplementedError, - "calling / on a linear expression is not supported, " - "please use CpModel.add_division_equality"); - }) - .def("__div__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error(PyExc_NotImplementedError, "calling / on a linear expression is not supported, " "please use CpModel.add_division_equality"); }) .def("__truediv__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { - throw_error(PyExc_NotImplementedError, - "calling // on a linear expression is not supported, " - "please use CpModel.add_division_equality"); - }) - .def("__truediv__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error(PyExc_NotImplementedError, "calling // on a linear expression is not supported, " "please use CpModel.add_division_equality"); }) .def("__mod__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { - throw_error(PyExc_NotImplementedError, - "calling %% on a linear expression is not supported, " - "please use CpModel.add_modulo_equality"); - }) - .def("__mod__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error(PyExc_NotImplementedError, "calling %% on a linear expression is not supported, " "please use CpModel.add_modulo_equality"); }) .def("__pow__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error(PyExc_NotImplementedError, "calling ** on a linear expression is not supported, " "please use CpModel.add_multiplication_equality"); }) - .def("__pow__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { - throw_error(PyExc_NotImplementedError, - "calling ** on a linear expression is not supported, " - "please use CpModel.add_multiplication_equality"); - }) - .def("__lshift__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { - throw_error( - PyExc_NotImplementedError, - "calling left shift on a linear expression is not supported"); - }) .def("__lshift__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error( PyExc_NotImplementedError, "calling left shift on a linear expression is not supported"); }) .def("__rshift__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { - throw_error( - PyExc_NotImplementedError, - "calling right shift on a linear expression is not supported"); - }) - .def("__rshift__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error( PyExc_NotImplementedError, "calling right shift on a linear expression is not supported"); }) .def("__and__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { - throw_error(PyExc_NotImplementedError, - "calling and on a linear expression is not supported"); - }) - .def("__and__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error(PyExc_NotImplementedError, "calling and on a linear expression is not supported"); }) .def("__or__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error(PyExc_NotImplementedError, "calling or on a linear expression is not supported"); }) - .def("__or__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { - throw_error(PyExc_NotImplementedError, - "calling or on a linear expression is not supported"); - }) - .def("__xor__", - [](LinearExpr* /*self*/, LinearExpr* /*other*/) { - throw_error(PyExc_NotImplementedError, - "calling xor on a linear expression is not supported"); - }) .def("__xor__", - [](LinearExpr* /*self*/, int64_t /*cst*/) { + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { throw_error(PyExc_NotImplementedError, "calling xor on a linear expression is not supported"); }) @@ -710,15 +631,10 @@ PYBIND11_MODULE(swig_helper, m) { .def("__bool__", [](LinearExpr* /*self*/) { throw_error(PyExc_NotImplementedError, "Evaluating a LinearExpr instance as a Boolean is " - "not implemented."); + "not supported."); }); - py::class_(m, "FloatAffine") - .def(py::init()) - .def_property_readonly("expression", &FloatAffine::expression) - .def_property_readonly("coefficient", &FloatAffine::coefficient) - .def_property_readonly("offset", &FloatAffine::offset); - + // Expose Internal classes, mostly for testing. py::class_(m, "CanonicalFloatExpression") .def(py::init()) .def_property_readonly("vars", &CanonicalFloatExpression::vars) @@ -732,15 +648,21 @@ PYBIND11_MODULE(swig_helper, m) { .def_property_readonly("offset", &CanonicalIntExpression::offset) .def_property_readonly("ok", &CanonicalIntExpression::ok); + py::class_(m, "FloatAffine") + .def(py::init()) + .def_property_readonly("expression", &FloatAffine::expression) + .def_property_readonly("coefficient", &FloatAffine::coefficient) + .def_property_readonly("offset", &FloatAffine::offset); + py::class_(m, "IntAffine") .def(py::init()) .def_property_readonly("expression", &IntAffine::expression) .def_property_readonly("coefficient", &IntAffine::coefficient) .def_property_readonly("offset", &IntAffine::offset); - py::class_(m, "Literal") + py::class_(m, "Literal", kLiteralClassDoc) .def_property_readonly("index", &Literal::index, - "The index of the variable in the model.") + "The index of the literal in the model.") .def("negated", &Literal::negated, R"doc( Returns the negation of a literal (a Boolean variable or its negation). @@ -755,8 +677,8 @@ PYBIND11_MODULE(swig_helper, m) { .def("__bool__", [](Literal* /*self*/) { throw_error(PyExc_NotImplementedError, - "Evaluating a Literal instance as a Boolean is " - "not implemented."); + "Evaluating a Literal as a Boolean valueis " + "not supported."); }) // PEP8 Compatibility. .def("Not", &Literal::negated) @@ -765,18 +687,19 @@ PYBIND11_MODULE(swig_helper, m) { // Memory management: // - The BaseIntVar owns the NotBooleanVariable. // - The NotBooleanVariable is created at the same time as the base variable - // when the variable is boolean. + // when the variable is Boolean, and is deleted when the base variable is + // deleted. // - The negated() methods return an internal reference to the negated // object. That means memory of the negated variable is onwed by the C++ // layer, but a reference is kept in python to link the lifetime of the // negated variable to the base variable. py::class_(m, "BaseIntVar") - .def(py::init()) - .def(py::init()) + .def(py::init()) // Integer variable. + .def(py::init()) // Potential Boolean variable. .def_property_readonly("index", &BaseIntVar::index, "The index of the variable in the model.") .def_property_readonly("is_boolean", &BaseIntVar::is_boolean, - "Whether the variable is boolean.") + "Whether the variable is Boolean.") .def("__str__", &BaseIntVar::ToString) .def("__repr__", &BaseIntVar::DebugString) .def( @@ -784,7 +707,7 @@ PYBIND11_MODULE(swig_helper, m) { [](BaseIntVar* self) { if (!self->is_boolean()) { throw_error(PyExc_TypeError, - "negated() is only supported for boolean variables."); + "negated() is only supported for Boolean variables."); } return self->negated(); }, @@ -795,7 +718,7 @@ PYBIND11_MODULE(swig_helper, m) { [](BaseIntVar* self) { if (!self->is_boolean()) { throw_error(PyExc_ValueError, - "negated() is only supported for boolean variables."); + "negated() is only supported for Boolean variables."); } return self->negated(); }, @@ -807,7 +730,7 @@ PYBIND11_MODULE(swig_helper, m) { [](BaseIntVar* self) { if (!self->is_boolean()) { throw_error(PyExc_ValueError, - "negated() is only supported for boolean variables."); + "negated() is only supported for Boolean variables."); } return self->negated(); }, @@ -848,7 +771,7 @@ PYBIND11_MODULE(swig_helper, m) { absl::StrCat("Evaluating a BoundedLinearExpression '", self.ToString(), "'instance as a Boolean is " - "not implemented.") + "not supported.") .c_str()); return false; }); diff --git a/ortools/sat/swig_helper.cc b/ortools/sat/swig_helper.cc index 8476b83490..2ce9d8f7fb 100644 --- a/ortools/sat/swig_helper.cc +++ b/ortools/sat/swig_helper.cc @@ -78,12 +78,12 @@ double SolutionCallback::BestObjectiveBound() const { return response_.best_objective_bound(); } -int64_t SolutionCallback::SolutionIntegerValue(int index) { +int64_t SolutionCallback::SolutionIntegerValue(int index) const { return index >= 0 ? response_.solution(index) : -response_.solution(-index - 1); } -bool SolutionCallback::SolutionBooleanValue(int index) { +bool SolutionCallback::SolutionBooleanValue(int index) const { return index >= 0 ? response_.solution(index) != 0 : response_.solution(-index - 1) == 0; } diff --git a/ortools/sat/swig_helper.h b/ortools/sat/swig_helper.h index 6d32e51b7d..f86a414001 100644 --- a/ortools/sat/swig_helper.h +++ b/ortools/sat/swig_helper.h @@ -58,9 +58,9 @@ class SolutionCallback { double BestObjectiveBound() const; - int64_t SolutionIntegerValue(int index); + int64_t SolutionIntegerValue(int index) const; - bool SolutionBooleanValue(int index); + bool SolutionBooleanValue(int index) const; // Stops the search. void StopSearch();