diff --git a/ortools/base/BUILD.bazel b/ortools/base/BUILD.bazel index c118a14e85..d15d1317b1 100644 --- a/ortools/base/BUILD.bazel +++ b/ortools/base/BUILD.bazel @@ -583,6 +583,12 @@ cc_library( deps = [":base"], ) +cc_library( + name = "string_view_migration", + hdrs = ["string_view_migration.h"], + deps = ["@abseil-cpp//absl/strings"], +) + cc_library( name = "strong_int", hdrs = ["strong_int.h"], diff --git a/ortools/base/string_view_migration.h b/ortools/base/string_view_migration.h new file mode 100644 index 0000000000..df76521299 --- /dev/null +++ b/ortools/base/string_view_migration.h @@ -0,0 +1,34 @@ +// Copyright 2010-2025 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_ORTOOLS_ORTOOLS_BASE_STRING_VIEW_MIGRATION_H_ +#define THIRD_PARTY_ORTOOLS_ORTOOLS_BASE_STRING_VIEW_MIGRATION_H_ + +#include + +#include "absl/strings/string_view.h" + +// This file contains helpers for various string_view migration efforts. These +// are not intended to be stable long-term. + +namespace google::protobuf { + +inline std::string StringCopy(absl::string_view str) { + return std::string(str); +} + +inline std::string StringCopy(const std::string& str) { return str; } + +} // namespace google::protobuf + +#endif // THIRD_PARTY_ORTOOLS_ORTOOLS_BASE_STRING_VIEW_MIGRATION_H_ diff --git a/ortools/linear_solver/wrappers/BUILD.bazel b/ortools/linear_solver/wrappers/BUILD.bazel index eb3bb8db25..1b55435df7 100644 --- a/ortools/linear_solver/wrappers/BUILD.bazel +++ b/ortools/linear_solver/wrappers/BUILD.bazel @@ -37,6 +37,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ "//ortools/base:file", + "//ortools/base:string_view_migration", "//ortools/linear_solver:linear_solver_cc_proto", "//ortools/linear_solver:model_exporter", "//ortools/linear_solver:solve_mp_model", diff --git a/ortools/linear_solver/wrappers/model_builder_helper.cc b/ortools/linear_solver/wrappers/model_builder_helper.cc index bc5bcdf5eb..610d158a42 100644 --- a/ortools/linear_solver/wrappers/model_builder_helper.cc +++ b/ortools/linear_solver/wrappers/model_builder_helper.cc @@ -30,14 +30,14 @@ #include "absl/strings/str_join.h" #include "ortools/base/helpers.h" #include "ortools/base/options.h" -#include "ortools/gurobi/environment.h" +#include "ortools/base/string_view_migration.h" +#include "ortools/linear_solver/gurobi_util.h" #include "ortools/linear_solver/linear_solver.h" #include "ortools/linear_solver/linear_solver.pb.h" #include "ortools/linear_solver/model_exporter.h" #include "ortools/linear_solver/proto_solver/glop_proto_solver.h" #include "ortools/linear_solver/proto_solver/gurobi_proto_solver.h" #include "ortools/linear_solver/proto_solver/sat_proto_solver.h" -#include "ortools/linear_solver/proto_solver/xpress_proto_solver.h" #include "ortools/linear_solver/solve_mp_model.h" #if defined(USE_SCIP) #include "ortools/linear_solver/proto_solver/scip_proto_solver.h" @@ -50,7 +50,6 @@ #endif // defined(USE_PDLP) #include "ortools/lp_data/lp_parser.h" #include "ortools/lp_data/mps_reader.h" -#include "ortools/xpress/environment.h" namespace operations_research { namespace mb { @@ -185,7 +184,7 @@ double ModelBuilderHelper::VarObjectiveCoefficient(int var_index) const { } std::string ModelBuilderHelper::VarName(int var_index) const { - return model_.variable(var_index).name(); + return google::protobuf::StringCopy(model_.variable(var_index).name()); } int ModelBuilderHelper::AddLinearConstraint() { @@ -261,7 +260,7 @@ double ModelBuilderHelper::ConstraintUpperBound(int ct_index) const { } std::string ModelBuilderHelper::ConstraintName(int ct_index) const { - return model_.constraint(ct_index).name(); + return google::protobuf::StringCopy(model_.constraint(ct_index).name()); } std::vector ModelBuilderHelper::ConstraintVarIndices(int ct_index) const { @@ -401,7 +400,8 @@ double ModelBuilderHelper::EnforcedConstraintUpperBound(int ct_index) const { std::string ModelBuilderHelper::EnforcedConstraintName(int ct_index) const { DCHECK(IsEnforcedConstraint(ct_index)); - return model_.general_constraint(ct_index).name(); + return google::protobuf::StringCopy( + model_.general_constraint(ct_index).name()); } std::vector ModelBuilderHelper::EnforcedConstraintVarIndices( @@ -438,7 +438,9 @@ int ModelBuilderHelper::num_constraints() const { return model_.constraint_size() + model_.general_constraint_size(); } -std::string ModelBuilderHelper::name() const { return model_.name(); } +std::string ModelBuilderHelper::name() const { + return google::protobuf::StringCopy(model_.name()); +} void ModelBuilderHelper::SetName(const std::string& name) { model_.set_name(name); @@ -557,11 +559,6 @@ bool ModelSolverHelper::SolverIsSupported() const { solver_type_.value() == MPModelRequest::GUROBI_LINEAR_PROGRAMMING) { return GurobiIsCorrectlyInstalled(); } - if (solver_type_.value() == - MPModelRequest::XPRESS_MIXED_INTEGER_PROGRAMMING || - solver_type_.value() == MPModelRequest::XPRESS_LINEAR_PROGRAMMING) { - return XpressIsCorrectlyInstalled(); - } return false; } @@ -635,12 +632,6 @@ void ModelSolverHelper::Solve(const ModelBuilderHelper& model) { break; } #endif // defined(USE_HIGHS) - case MPModelRequest:: - XPRESS_LINEAR_PROGRAMMING: // ABSL_FALLTHROUGH_INTENDED - case MPModelRequest::XPRESS_MIXED_INTEGER_PROGRAMMING: { - response_ = XPressSolveProto(request); - break; - } default: { response_->set_status( MPSolverResponseStatus::MPSOLVER_SOLVER_TYPE_UNAVAILABLE); @@ -754,7 +745,7 @@ double ModelSolverHelper::activity(int ct_index) { std::string ModelSolverHelper::status_string() const { if (!has_response()) return ""; - return response_.value().status_str(); + return google::protobuf::StringCopy(response_.value().status_str()); } double ModelSolverHelper::wall_time() const { diff --git a/ortools/math_opt/core/BUILD.bazel b/ortools/math_opt/core/BUILD.bazel index bdd9db9758..dd45ee0207 100644 --- a/ortools/math_opt/core/BUILD.bazel +++ b/ortools/math_opt/core/BUILD.bazel @@ -64,6 +64,7 @@ cc_library( "//ortools/base:status_macros", "//ortools/math_opt:model_cc_proto", "//ortools/math_opt:model_update_cc_proto", + "//ortools/base:string_view_migration", "@abseil-cpp//absl/algorithm:container", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/log:check", diff --git a/ortools/math_opt/core/model_summary.h b/ortools/math_opt/core/model_summary.h index 061000751e..9c481a3998 100644 --- a/ortools/math_opt/core/model_summary.h +++ b/ortools/math_opt/core/model_summary.h @@ -32,6 +32,7 @@ #include "absl/types/span.h" #include "ortools/base/linked_hash_map.h" #include "ortools/base/status_macros.h" +#include "ortools/base/string_view_migration.h" #include "ortools/math_opt/model.pb.h" #include "ortools/math_opt/model_update.pb.h" @@ -256,7 +257,8 @@ absl::Status UpdateBiMapFromMappedData( } absl::c_sort(new_ids); for (const int64_t id : new_ids) { - RETURN_IF_ERROR(bimap.Insert(id, proto_map.at(id).name())); + RETURN_IF_ERROR(bimap.Insert( + id, google::protobuf::StringCopy(proto_map.at(id).name()))); } return absl::OkStatus(); } diff --git a/ortools/math_opt/cpp/BUILD.bazel b/ortools/math_opt/cpp/BUILD.bazel index 2a62a12df6..1bac17c9cc 100644 --- a/ortools/math_opt/cpp/BUILD.bazel +++ b/ortools/math_opt/cpp/BUILD.bazel @@ -196,6 +196,7 @@ cc_library( ":variable_and_expressions", "//ortools/base:protoutil", "//ortools/base:status_macros", + "//ortools/base:string_view_migration", "//ortools/gscip:gscip_cc_proto", "//ortools/math_opt:result_cc_proto", "//ortools/math_opt:solution_cc_proto", @@ -386,6 +387,7 @@ cc_library( srcs = ["streamable_solver_init_arguments.cc"], hdrs = ["streamable_solver_init_arguments.h"], deps = [ + "//ortools/base:string_view_migration", "//ortools/math_opt:parameters_cc_proto", "//ortools/math_opt/solvers:gurobi_cc_proto", "@abseil-cpp//absl/status:statusor", diff --git a/ortools/math_opt/cpp/solve_result.cc b/ortools/math_opt/cpp/solve_result.cc index ae0cab0da0..caf28a4e75 100644 --- a/ortools/math_opt/cpp/solve_result.cc +++ b/ortools/math_opt/cpp/solve_result.cc @@ -33,6 +33,7 @@ #include "absl/types/span.h" #include "ortools/base/protoutil.h" #include "ortools/base/status_macros.h" +#include "ortools/base/string_view_migration.h" #include "ortools/math_opt/core/math_opt_proto_utils.h" #include "ortools/math_opt/cpp/linear_constraint.h" #include "ortools/math_opt/cpp/variable_and_expressions.h" @@ -373,7 +374,7 @@ absl::StatusOr Termination::FromProto( return absl::InvalidArgumentError("reason must be specified"); } Termination result(/*is_maximize=*/false, *reason, - termination_proto.detail()); + google::protobuf::StringCopy(termination_proto.detail())); result.limit = EnumFromProto(termination_proto.limit()); OR_ASSIGN_OR_RETURN3( result.problem_status, diff --git a/ortools/math_opt/cpp/streamable_solver_init_arguments.cc b/ortools/math_opt/cpp/streamable_solver_init_arguments.cc index 3d0eea1b74..e2248fa306 100644 --- a/ortools/math_opt/cpp/streamable_solver_init_arguments.cc +++ b/ortools/math_opt/cpp/streamable_solver_init_arguments.cc @@ -16,6 +16,7 @@ #include #include "absl/status/statusor.h" +#include "ortools/base/string_view_migration.h" #include "ortools/math_opt/parameters.pb.h" #include "ortools/math_opt/solvers/gurobi.pb.h" @@ -34,10 +35,11 @@ GurobiInitializerProto::ISVKey GurobiISVKey::Proto() const { GurobiISVKey GurobiISVKey::FromProto( const GurobiInitializerProto::ISVKey& key_proto) { return GurobiISVKey{ - .name = key_proto.name(), - .application_name = key_proto.application_name(), + .name = google::protobuf::StringCopy(key_proto.name()), + .application_name = + google::protobuf::StringCopy(key_proto.application_name()), .expiration = key_proto.expiration(), - .key = key_proto.key(), + .key = google::protobuf::StringCopy(key_proto.key()), }; } diff --git a/ortools/math_opt/elemental/BUILD.bazel b/ortools/math_opt/elemental/BUILD.bazel index 2ebdec571a..4559db384f 100644 --- a/ortools/math_opt/elemental/BUILD.bazel +++ b/ortools/math_opt/elemental/BUILD.bazel @@ -62,6 +62,7 @@ cc_library( ":symmetry", ":thread_safe_id_map", "//ortools/base:status_macros", + "//ortools/base:string_view_migration", "//ortools/math_opt:model_cc_proto", "//ortools/math_opt:model_update_cc_proto", "//ortools/math_opt:sparse_containers_cc_proto", diff --git a/ortools/math_opt/elemental/elemental_from_proto.cc b/ortools/math_opt/elemental/elemental_from_proto.cc index 50f5afa087..313b24f432 100644 --- a/ortools/math_opt/elemental/elemental_from_proto.cc +++ b/ortools/math_opt/elemental/elemental_from_proto.cc @@ -26,6 +26,7 @@ #include "google/protobuf/repeated_ptr_field.h" #include "ortools/base/status_builder.h" #include "ortools/base/status_macros.h" +#include "ortools/base/string_view_migration.h" #include "ortools/math_opt/core/model_summary.h" #include "ortools/math_opt/elemental/attr_key.h" #include "ortools/math_opt/elemental/attributes.h" @@ -253,7 +254,8 @@ absl::StatusOr ElementalFromModelProtoImpl(const ModelProto& proto) { return absl::UnimplementedError( "Elemental does not support sos2 constraints yet"); } - Elemental elemental(proto.name(), proto.objective().name()); + Elemental elemental(google::protobuf::StringCopy(proto.name()), + google::protobuf::StringCopy(proto.objective().name())); AddVariables(proto.variables(), elemental); { const ObjectiveProto& objective = proto.objective(); diff --git a/ortools/sat/BUILD.bazel b/ortools/sat/BUILD.bazel index ba675c7341..58a506a26a 100644 --- a/ortools/sat/BUILD.bazel +++ b/ortools/sat/BUILD.bazel @@ -33,6 +33,7 @@ cc_library( ":model", ":sat_parameters_cc_proto", "//ortools/util:sorted_interval_list", + "//ortools/base:string_view_migration", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/strings", diff --git a/ortools/sat/cp_model.cc b/ortools/sat/cp_model.cc index 30a888d3d8..ff19e87aae 100644 --- a/ortools/sat/cp_model.cc +++ b/ortools/sat/cp_model.cc @@ -26,6 +26,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" +#include "ortools/base/string_view_migration.h" #include "ortools/sat/cp_model.pb.h" #include "ortools/sat/cp_model_utils.h" #include "ortools/util/sorted_interval_list.h" @@ -125,7 +126,8 @@ IntVar IntVar::WithName(absl::string_view name) { std::string IntVar::Name() const { if (builder_ == nullptr) return "null"; - return builder_->Proto().variables(index_).name(); + return google::protobuf::StringCopy( + builder_->Proto().variables(index_).name()); } ::operations_research::Domain IntVar::Domain() const { @@ -619,7 +621,8 @@ BoolVar IntervalVar::PresenceBoolVar() const { std::string IntervalVar::Name() const { if (builder_ == nullptr) return "null"; - return builder_->Proto().constraints(index_).name(); + return google::protobuf::StringCopy( + builder_->Proto().constraints(index_).name()); } std::string IntervalVar::DebugString() const { diff --git a/ortools/sat/python/BUILD.bazel b/ortools/sat/python/BUILD.bazel index d73f0fa96f..ff0364fcba 100644 --- a/ortools/sat/python/BUILD.bazel +++ b/ortools/sat/python/BUILD.bazel @@ -28,6 +28,7 @@ cc_library( hdrs = ["linear_expr.h"], deps = [ "//ortools/sat:cp_model_cc_proto", + "//ortools/sat:cp_model_utils", "//ortools/util:fp_roundtrip_conv", "//ortools/util:sorted_interval_list", "@abseil-cpp//absl/container:btree", @@ -38,6 +39,48 @@ cc_library( ], ) +cc_library( + name = "wrappers", + srcs = ["wrappers.cc"], + hdrs = ["wrappers.h"], + deps = [ + "@abseil-cpp//absl/base:nullability", + "@abseil-cpp//absl/container:flat_hash_map", + "@abseil-cpp//absl/container:flat_hash_set", + "@abseil-cpp//absl/log", + "@abseil-cpp//absl/log:check", + "@abseil-cpp//absl/log:die_if_null", + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/types:span", + "@protobuf", + ], +) + +cc_binary( + name = "gen_proto_builder_pybind11", + srcs = ["gen_proto_builder_pybind11.cc"], + deps = [ + ":wrappers", + "//ortools/base", + "//ortools/sat:cp_model_cc_proto", + "//ortools/sat:sat_parameters_cc_proto", + "@abseil-cpp//absl/log:die_if_null", + "@abseil-cpp//absl/strings:str_format", + ], +) + +genrule( + name = "run_gen_proto_builder_pybind11", + outs = ["proto_builder_pybind11.h"], + cmd = "$(location :gen_proto_builder_pybind11) > $@", + tools = [":gen_proto_builder_pybind11"], +) + +cc_library( + name = "proto_builder_pybind11", + hdrs = ["proto_builder_pybind11.h"], +) + pybind_extension( name = "cp_model_helper", srcs = ["cp_model_helper.cc"], @@ -45,12 +88,12 @@ pybind_extension( deps = [ ":linear_expr", ":linear_expr_doc", + ":proto_builder_pybind11", "//ortools/sat:cp_model_cc_proto", "//ortools/sat:cp_model_utils", "//ortools/sat:sat_parameters_cc_proto", "//ortools/sat:swig_helper", "@abseil-cpp//absl/strings", - "@pybind11_protobuf//pybind11_protobuf:native_proto_caster", ], ) @@ -59,44 +102,19 @@ py_test( srcs = ["cp_model_helper_test.py"], deps = [ ":cp_model_helper", - "//ortools/sat:cp_model_py_pb2", - "//ortools/sat:sat_parameters_py_pb2", "//ortools/util/python:sorted_interval_list", requirement("absl-py"), ], ) -py_library( - name = "cp_model_numbers", - srcs = ["cp_model_numbers.py"], - visibility = ["//visibility:public"], - deps = [ - ":cp_model_helper", - requirement("numpy"), - "@protobuf//:protobuf_python", - ], -) - -py_test( - name = "cp_model_numbers_test", - srcs = ["cp_model_numbers_test.py"], - deps = [ - ":cp_model_numbers", - requirement("absl-py"), - ], -) - py_library( name = "cp_model", srcs = ["cp_model.py"], visibility = ["//visibility:public"], deps = [ ":cp_model_helper", - ":cp_model_numbers", requirement("numpy"), requirement("pandas"), - "//ortools/sat:cp_model_py_pb2", - "//ortools/sat:sat_parameters_py_pb2", "//ortools/util/python:sorted_interval_list", ], ) diff --git a/ortools/sat/python/cp_model_helper.cc b/ortools/sat/python/cp_model_helper.cc index 10e57c7657..50f5bc2752 100644 --- a/ortools/sat/python/cp_model_helper.cc +++ b/ortools/sat/python/cp_model_helper.cc @@ -25,11 +25,14 @@ #include "absl/functional/any_invocable.h" #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" +#include "ortools/base/string_view_migration.h" +#include "ortools/port/proto_utils.h" // IWYU: keep #include "ortools/sat/cp_model.pb.h" #include "ortools/sat/cp_model_utils.h" #include "ortools/sat/python/linear_expr.h" #include "ortools/sat/python/linear_expr_doc.h" #include "ortools/sat/swig_helper.h" +#include "ortools/util/saturated_arithmetic.h" #include "ortools/util/sorted_interval_list.h" #include "pybind11/attr.h" #include "pybind11/cast.h" @@ -39,7 +42,6 @@ #include "pybind11/pybind11.h" #include "pybind11/pytypes.h" #include "pybind11/stl.h" -#include "pybind11_protobuf/native_proto_caster.h" namespace py = pybind11; @@ -83,28 +85,6 @@ class PySolutionCallback : public SolutionCallback { } }; -// A trampoline class to override the __str__ and __repr__ methods. -class PyBaseIntVar : public BaseIntVar { - public: - using BaseIntVar::BaseIntVar; /* Inherit constructors */ - - std::string ToString() const override { - PYBIND11_OVERRIDE_PURE_NAME(std::string, // Return type (ret_type) - BaseIntVar, // Parent class (cname) - "__str__", // Name of method in Python (name) - ToString, // Name of function in C++ (fn) - ); - } - - std::string DebugString() const override { - PYBIND11_OVERRIDE_PURE_NAME(std::string, // Return type (ret_type) - BaseIntVar, // Parent class (cname) - "__repr__", // Name of method in Python (name) - DebugString, // Name of function in C++ (fn) - ); - } -}; - // A class to wrap a C++ CpSolverResponse in a Python object, avoid the proto // conversion back to python. class ResponseWrapper { @@ -151,7 +131,9 @@ class ResponseWrapper { return CpSatHelper::SolverResponseStats(response_); } - std::string SolutionInfo() const { return response_.solution_info(); } + std::string SolutionInfo() const { + return google::protobuf::StringCopy(response_.solution_info()); + } std::vector SufficientAssumptionsForInfeasibility() const { return std::vector( @@ -432,8 +414,172 @@ std::shared_ptr WeightedSumArguments(py::sequence expressions, } } +void LinearExprToProto(const py::handle& arg, int64_t multiplier, + LinearExpressionProto* proto) { + proto->Clear(); + if (py::isinstance(arg)) { + std::shared_ptr expr = arg.cast>(); + IntExprVisitor visitor; + visitor.AddToProcess(expr, multiplier); + std::vector> vars; + std::vector coeffs; + int64_t offset = 0; + if (!visitor.Process(&vars, &coeffs, &offset)) { + ThrowError(PyExc_ValueError, + absl::StrCat("Failed to convert integer linear expression: ", + expr->DebugString())); + } + for (const auto& var : vars) { + proto->add_vars(var->index()); + } + for (const int64_t coeff : coeffs) { + proto->add_coeffs(coeff); + } + proto->set_offset(offset); + } else if (py::isinstance(arg)) { + int64_t value = arg.cast(); + proto->set_offset(value * multiplier); + } else { + py::type objtype = py::type::of(arg); + const std::string type_name = objtype.attr("__name__").cast(); + ThrowError(PyExc_TypeError, + absl::StrCat("Cannot convert '", absl::CEscape(type_name), + "' to a linear expression.")); + } +} + +int AddBoundedLinearExpressionToModel( + BoundedLinearExpression* ble, std::shared_ptr model_proto) { + const int index = model_proto->constraints_size(); + ConstraintProto* ct = model_proto->add_constraints(); + for (const auto& var : ble->vars()) { + ct->mutable_linear()->add_vars(var->index()); + } + for (const int64_t coeff : ble->coeffs()) { + ct->mutable_linear()->add_coeffs(coeff); + } + const int64_t offset = ble->offset(); + const Domain& bounds = ble->bounds(); + for (const int64_t bound : bounds.FlattenedIntervals()) { + if (bound == std::numeric_limits::min() || + bound == std::numeric_limits::max()) { + ct->mutable_linear()->add_domain(bound); + } else { + ct->mutable_linear()->add_domain(CapSub(bound, offset)); + } + } + return index; +} + +int AddBoolOr(const std::vector& literals, + std::shared_ptr model_proto) { + const int index = model_proto->constraints_size(); + ConstraintProto* ct = model_proto->add_constraints(); + ct->mutable_bool_or()->mutable_literals()->Add(literals.begin(), + literals.end()); + return index; +} + +int AddBoolAnd(const std::vector& literals, + std::shared_ptr model_proto) { + const int index = model_proto->constraints_size(); + ConstraintProto* ct = model_proto->add_constraints(); + ct->mutable_bool_and()->mutable_literals()->Add(literals.begin(), + literals.end()); + return index; +} + +int AddBoolXOr(const std::vector& literals, + std::shared_ptr model_proto) { + const int index = model_proto->constraints_size(); + ConstraintProto* ct = model_proto->add_constraints(); + ct->mutable_bool_xor()->mutable_literals()->Add(literals.begin(), + literals.end()); + return index; +} + +int AddAtMostOne(const std::vector& literals, + std::shared_ptr model_proto) { + const int index = model_proto->constraints_size(); + ConstraintProto* ct = model_proto->add_constraints(); + ct->mutable_at_most_one()->mutable_literals()->Add(literals.begin(), + literals.end()); + return index; +} + +int AddExactlyOne(const std::vector& literals, + std::shared_ptr model_proto) { + const int index = model_proto->constraints_size(); + ConstraintProto* ct = model_proto->add_constraints(); + ct->mutable_exactly_one()->mutable_literals()->Add(literals.begin(), + literals.end()); + return index; +} + +int AddElement(const py::handle& index, py::sequence exprs, + const py::handle& target, + std::shared_ptr model_proto) { + const int ct_index = model_proto->constraints_size(); + ConstraintProto* ct = model_proto->add_constraints(); + LinearExprToProto(index, 1, ct->mutable_element()->mutable_linear_index()); + for (const auto& expr : exprs) { + LinearExprToProto(expr, 1, ct->mutable_element()->add_exprs()); + } + LinearExprToProto(target, 1, ct->mutable_element()->mutable_linear_target()); + return ct_index; +} + +int AddLinearArgumentConstraint(const std::string& name, + const py::handle& target, py::sequence exprs, + std::shared_ptr model_proto) { + const int ct_index = model_proto->constraints_size(); + ConstraintProto* ct = model_proto->add_constraints(); + LinearArgumentProto* proto; + int64_t multiplier = 1; + if (name == "min") { + proto = ct->mutable_lin_max(); + multiplier = -1; + } else if (name == "max") { + proto = ct->mutable_lin_max(); + } else if (name == "prod") { + proto = ct->mutable_int_prod(); + } else if (name == "div") { + proto = ct->mutable_int_div(); + } else if (name == "mod") { + proto = ct->mutable_int_mod(); + } else { + ThrowError(PyExc_ValueError, + absl::StrCat("Unknown integer argument constraint: ", name)); + } + + LinearExprToProto(target, multiplier, proto->mutable_target()); + for (const auto& expr : exprs) { + LinearExprToProto(expr, multiplier, proto->add_exprs()); + } + + return ct_index; +} + +void AddEnforcementLiterals(int index, const std::vector& literals, + std::shared_ptr model_proto) { + ConstraintProto* ct = model_proto->mutable_constraints(index); + ct->mutable_enforcement_literal()->Add(literals.begin(), literals.end()); +} + +void SetCtName(int index, const std::string& name, + std::shared_ptr model_proto) { + model_proto->mutable_constraints(index)->set_name(name); +} + +std::string GetCtName(int index, std::shared_ptr model_proto) { + return model_proto->constraints(index).name(); +} + +void ClearCtName(int index, std::shared_ptr model_proto) { + model_proto->mutable_constraints(index)->clear_name(); +} + PYBIND11_MODULE(cp_model_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 @@ -575,28 +721,35 @@ PYBIND11_MODULE(cp_model_helper, m) { solve_wrapper->AddBestBoundCallback(safe_best_bound_callback); }, py::arg("best_bound_callback").none(false)) - .def("set_parameters", &SolveWrapper::SetParameters, - py::arg("parameters")) - .def("solve", - [](ExtSolveWrapper* solve_wrapper, - const CpModelProto& model_proto) -> CpSolverResponse { - const auto result = [&]() -> CpSolverResponse { - ::py::gil_scoped_release release; - return solve_wrapper->Solve(model_proto); - }(); - if (solve_wrapper->local_error_already_set_.has_value()) { - solve_wrapper->local_error_already_set_->restore(); - solve_wrapper->local_error_already_set_.reset(); - throw py::error_already_set(); - } - return result; - }) + .def( + "set_parameters", + [](ExtSolveWrapper* solve_wrapper, + std::shared_ptr parameters) { + solve_wrapper->SetParameters(*parameters); + }, + py::arg("parameters").none(false)) + .def( + "solve", + [](ExtSolveWrapper* solve_wrapper, + std::shared_ptr model_proto) -> CpSolverResponse { + const auto result = [=]() -> CpSolverResponse { + ::py::gil_scoped_release release; + return solve_wrapper->Solve(*model_proto); + }(); + if (solve_wrapper->local_error_already_set_.has_value()) { + solve_wrapper->local_error_already_set_->restore(); + solve_wrapper->local_error_already_set_.reset(); + throw py::error_already_set(); + } + return result; + }, + py::arg("model_proto").none(false)) .def("solve_and_return_response_wrapper", [](ExtSolveWrapper* solve_wrapper, - const CpModelProto& model_proto) -> ResponseWrapper { - const auto result = [&]() -> ResponseWrapper { + std::shared_ptr model_proto) -> ResponseWrapper { + const auto result = [=]() -> ResponseWrapper { ::py::gil_scoped_release release; - return ResponseWrapper(solve_wrapper->Solve(model_proto)); + return ResponseWrapper(solve_wrapper->Solve(*model_proto)); }(); if (solve_wrapper->local_error_already_set_.has_value()) { solve_wrapper->local_error_already_set_->restore(); @@ -617,7 +770,36 @@ PYBIND11_MODULE(cp_model_helper, m) { .def_static("variable_domain", &CpSatHelper::VariableDomain, py::arg("variable_proto")) .def_static("write_model_to_file", &CpSatHelper::WriteModelToFile, - py::arg("model_proto"), py::arg("filename")); + py::arg("model_proto"), py::arg("filename")) + .def_static("set_ct_name", &SetCtName, py::arg("index"), py::arg("name"), + py::arg("model_proto")) + .def_static("ct_name", &GetCtName, py::arg("index"), + py::arg("model_proto")) + .def_static("clear_ct_name", &ClearCtName, py::arg("index"), + py::arg("model_proto")) + .def_static("add_bool_or", &AddBoolOr, py::arg("literals"), + py::arg("model_proto").none(false)) + .def_static("add_bool_and", &AddBoolAnd, py::arg("literals"), + py::arg("model_proto").none(false)) + .def_static("add_bool_xor", &AddBoolXOr, py::arg("literals"), + py::arg("model_proto").none(false)) + .def_static("add_at_most_one", &AddAtMostOne, py::arg("literals"), + py::arg("model_proto").none(false)) + .def_static("add_exactly_one", &AddExactlyOne, py::arg("literals"), + py::arg("model_proto").none(false)) + .def_static("add_element", &AddElement, py::arg("index").none(false), + py::arg("expressions"), py::arg("target").none(false), + py::arg("model_proto").none(false)) + .def_static("add_linear_argument_constraint", + &AddLinearArgumentConstraint, py::arg("name").none(false), + py::arg("target").none(false), py::arg("exprs"), + py::arg("model_proto").none(false)) + .def_static("add_enforcement_literals", &AddEnforcementLiterals, + py::arg("index"), py::arg("literals"), + py::arg("model_proto").none(false)) + .def_static("add_bounded_linear_expression_to_model", + &AddBoundedLinearExpressionToModel, py::arg("ble"), + py::arg("model_proto")); py::class_>( m, "LinearExpr", DOC(operations_research, sat, python, LinearExpr)) @@ -892,31 +1074,27 @@ PYBIND11_MODULE(cp_model_helper, m) { py::init>, int64_t, double>()) .def( "__add__", - [](py::object self, + [](std::shared_ptr expr, std::shared_ptr other) -> std::shared_ptr { - const int num_uses = Py_REFCNT(self.ptr()); - std::shared_ptr expr = - self.cast>(); + const int num_uses = Py_REFCNT(py::cast(expr).ptr()); return (num_uses == 4) ? expr->AddInPlace(other) : expr->Add(other); }, py::arg("other").none(false), DOC(operations_research, sat, python, LinearExpr, Add)) .def( "__add__", - [](py::object self, int64_t cst) -> std::shared_ptr { - const int num_uses = Py_REFCNT(self.ptr()); - std::shared_ptr expr = - self.cast>(); + [](std::shared_ptr expr, + int64_t cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(py::cast(expr).ptr()); return (num_uses == 4) ? expr->AddIntInPlace(cst) : expr->AddInt(cst); }, DOC(operations_research, sat, python, LinearExpr, AddInt)) .def( "__add__", - [](py::object self, double cst) -> std::shared_ptr { - const int num_uses = Py_REFCNT(self.ptr()); - std::shared_ptr expr = - self.cast>(); + [](std::shared_ptr expr, + double cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(py::cast(expr).ptr()); return (num_uses == 4) ? expr->AddFloatInPlace(cst) : expr->AddFloat(cst); }, @@ -924,10 +1102,9 @@ PYBIND11_MODULE(cp_model_helper, m) { DOC(operations_research, sat, python, LinearExpr, AddFloat)) .def( "__radd__", - [](py::object self, int64_t cst) -> std::shared_ptr { - const int num_uses = Py_REFCNT(self.ptr()); - std::shared_ptr expr = - self.cast>(); + [](std::shared_ptr expr, + int64_t cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(py::cast(expr).ptr()); return (num_uses == 4) ? expr->AddIntInPlace(cst) : expr->AddInt(cst); }, @@ -935,10 +1112,9 @@ PYBIND11_MODULE(cp_model_helper, m) { DOC(operations_research, sat, python, LinearExpr, AddInt)) .def( "__radd__", - [](py::object self, double cst) -> std::shared_ptr { - const int num_uses = Py_REFCNT(self.ptr()); - std::shared_ptr expr = - self.cast>(); + [](std::shared_ptr expr, + double cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(py::cast(expr).ptr()); return (num_uses == 4) ? expr->AddFloatInPlace(cst) : expr->AddFloat(cst); }, @@ -969,11 +1145,9 @@ PYBIND11_MODULE(cp_model_helper, m) { DOC(operations_research, sat, python, LinearExpr, AddFloat)) .def( "__sub__", - [](py::object self, + [](std::shared_ptr expr, std::shared_ptr other) -> std::shared_ptr { - const int num_uses = Py_REFCNT(self.ptr()); - std::shared_ptr expr = - self.cast>(); + const int num_uses = Py_REFCNT(py::cast(expr).ptr()); return (num_uses == 4) ? expr->AddInPlace(other->Neg()) : expr->Sub(other); }, @@ -981,10 +1155,9 @@ PYBIND11_MODULE(cp_model_helper, m) { DOC(operations_research, sat, python, LinearExpr, Sub)) .def( "__sub__", - [](py::object self, int64_t cst) -> std::shared_ptr { - const int num_uses = Py_REFCNT(self.ptr()); - std::shared_ptr expr = - self.cast>(); + [](std::shared_ptr expr, + int64_t cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(py::cast(expr).ptr()); return (num_uses == 4) ? expr->AddIntInPlace(-cst) : expr->SubInt(cst); }, @@ -992,10 +1165,9 @@ PYBIND11_MODULE(cp_model_helper, m) { DOC(operations_research, sat, python, LinearExpr, SubInt)) .def( "__sub__", - [](py::object self, double cst) -> std::shared_ptr { - const int num_uses = Py_REFCNT(self.ptr()); - std::shared_ptr expr = - self.cast>(); + [](std::shared_ptr expr, + double cst) -> std::shared_ptr { + const int num_uses = Py_REFCNT(py::cast(expr).ptr()); return (num_uses == 4) ? expr->AddFloatInPlace(-cst) : expr->SubFloat(cst); }, @@ -1065,52 +1237,99 @@ PYBIND11_MODULE(cp_model_helper, m) { .def("Not", &Literal::negated) .def("Index", &Literal::index); - // Memory management: - // - The BaseIntVar owns the NotBooleanVariable and keeps a shared_ptr to it. - // - The NotBooleanVariable is created on demand, and is deleted when the base - // variable is deleted. It holds a weak_ptr to the base variable. - py::class_, Literal>( - m, "BaseIntVar", DOC(operations_research, sat, python, BaseIntVar)) - .def(py::init()) // Integer variable. - .def(py::init()) // Potential Boolean variable. + // IntVar and NotBooleanVariable both hold a shared_ptr to the model_proto. + py::class_, Literal>( + m, "IntVar", DOC(operations_research, sat, python, IntVar)) + .def(py::init, int>()) + .def(py::init>()) // new variable. .def_property_readonly( - "index", &BaseIntVar::index, - DOC(operations_research, sat, python, BaseIntVar, index)) + "proto", &IntVar::proto, py::return_value_policy::reference, + py::keep_alive<1, 0>() + // DOC(operations_research, sat, python, IntVar, proto) + ) .def_property_readonly( - "is_boolean", &BaseIntVar::is_boolean, - DOC(operations_research, sat, python, BaseIntVar, is_boolean)) - .def("__str__", &BaseIntVar::ToString) - .def("__repr__", &BaseIntVar::DebugString) + "model_proto", &IntVar::model_proto + // DOC(operations_research, sat, python, IntVar, model_proto) + ) + .def_property_readonly( + "index", &IntVar::index, py::return_value_policy::reference, + DOC(operations_research, sat, python, IntVar, index)) + .def_property_readonly( + "is_boolean", &IntVar::is_boolean, + DOC(operations_research, sat, python, IntVar, is_boolean)) + .def_property( + "name", &IntVar::name, &IntVar::SetName //, py::arg("name") + // DOC(operations_research, + // sat, python, IntVar, name) + ) + .def( + "with_name", + [](std::shared_ptr self, const std::string& name) { + self->SetName(name); + return self; + }, + py::arg("name")) + .def_property( + "domain", &IntVar::domain, &IntVar::SetDomain //, py::arg("domain") + // DOC(operations_research, sat, python, IntVar, domain) + ) + .def( + "with_domain", + [](std::shared_ptr self, const Domain& domain) { + self->SetDomain(domain); + return self; + }, + py::arg("domain")) + .def("__str__", &IntVar::ToString) + .def("__repr__", &IntVar::DebugString) .def( "negated", - [](std::shared_ptr self) { + [](std::shared_ptr self) { if (!self->is_boolean()) { ThrowError(PyExc_TypeError, "negated() is only supported for Boolean variables."); } return self->negated(); }, - DOC(operations_research, sat, python, BaseIntVar, negated)) + DOC(operations_research, sat, python, IntVar, negated)) .def( "__invert__", - [](std::shared_ptr self) { + [](std::shared_ptr self) { if (!self->is_boolean()) { ThrowError(PyExc_TypeError, "negated() is only supported for Boolean variables."); } return self->negated(); }, - DOC(operations_research, sat, python, BaseIntVar, negated)) + DOC(operations_research, sat, python, IntVar, negated)) + .def("__copy__", + [](const std::shared_ptr& self) { + return std::make_shared(self->model_proto(), + self->index()); + }) + .def(py::pickle( + [](std::shared_ptr p) { // __getstate__ + /* Return a tuple that fully encodes the state of the object */ + return py::make_tuple(p->model_proto(), p->index()); + }, + [](py::tuple t) { // __setstate__ + if (t.size() != 2) throw std::runtime_error("Invalid state!"); + + return std::make_shared( + t[0].cast>(), t[1].cast()); + })) // PEP8 Compatibility. + .def("Name", &IntVar::name) + .def("Proto", &IntVar::proto) .def("Not", - [](std::shared_ptr self) { + [](std::shared_ptr self) { if (!self->is_boolean()) { ThrowError(PyExc_TypeError, "negated() is only supported for Boolean variables."); } return self->negated(); }) - .def("Index", &BaseIntVar::index); + .def("Index", &IntVar::index); py::class_, Literal>( m, "NotBooleanVariable", @@ -1118,61 +1337,32 @@ PYBIND11_MODULE(cp_model_helper, m) { .def_property_readonly( "index", [](std::shared_ptr not_var) -> int { - if (!not_var->ok()) { - ThrowError(PyExc_ReferenceError, - "The base variable is not valid."); - } return not_var->index(); }, DOC(operations_research, sat, python, NotBooleanVariable, index)) .def("__str__", [](std::shared_ptr not_var) -> std::string { - if (!not_var->ok()) { - ThrowError(PyExc_ReferenceError, - "The base variable is not valid."); - } return not_var->ToString(); }) .def("__repr__", [](std::shared_ptr not_var) -> std::string { - if (!not_var->ok()) { - ThrowError(PyExc_ReferenceError, - "The base variable is not valid."); - } return not_var->DebugString(); }) .def( "negated", [](std::shared_ptr not_var) - -> std::shared_ptr { - if (!not_var->ok()) { - ThrowError(PyExc_ReferenceError, - "The base variable is not valid."); - } - return not_var->negated(); - }, + -> std::shared_ptr { return not_var->negated(); }, DOC(operations_research, sat, python, NotBooleanVariable, negated)) .def( "__invert__", [](std::shared_ptr not_var) - -> std::shared_ptr { - if (!not_var->ok()) { - ThrowError(PyExc_ReferenceError, - "The base variable is not valid."); - } - return not_var->negated(); - }, + -> std::shared_ptr { return not_var->negated(); }, DOC(operations_research, sat, python, NotBooleanVariable, negated)) + // PEP8 Compatibility. .def( "Not", [](std::shared_ptr not_var) - -> std::shared_ptr { - if (!not_var->ok()) { - ThrowError(PyExc_ReferenceError, - "The base variable is not valid."); - } - return not_var->negated(); - }, + -> std::shared_ptr { return not_var->negated(); }, DOC(operations_research, sat, python, NotBooleanVariable, negated)); py::class_>( @@ -1198,6 +1388,9 @@ PYBIND11_MODULE(cp_model_helper, m) { "not supported.")); return false; }); +#define IMPORT_PROTO_WRAPPER_CODE +#include "ortools/sat/python/proto_builder_pybind11.h" +#undef IMPORT_PROTO_WRAPPER_CODE } // NOLINT(readability/fn_size) } // namespace operations_research::sat::python