backport from main

This commit is contained in:
Corentin Le Molgat
2025-11-05 12:03:24 +01:00
parent b4b226801b
commit c8935fcfdf
5 changed files with 171 additions and 58 deletions

View File

@@ -131,7 +131,6 @@ cc_library(
name = "hungarian",
srcs = ["hungarian.cc"],
hdrs = ["hungarian.h"],
visibility = ["//visibility:public"],
deps = [
"//ortools/base",
"@abseil-cpp//absl/container:flat_hash_map",
@@ -158,7 +157,6 @@ cc_test(
cc_library(
name = "adjustable_k_ary_heap",
hdrs = ["adjustable_k_ary_heap.h"],
visibility = ["//visibility:public"],
deps = ["@abseil-cpp//absl/log:check"],
)

View File

@@ -25,7 +25,6 @@
#ifndef ORTOOLS_BASE_PYTHON_SWIG_H_
#define ORTOOLS_BASE_PYTHON_SWIG_H_
#if PY_VERSION_HEX >= 0x03030000 // Py3.3+
// Use Py3 unicode str() type for C++ strings.
#ifdef PyString_FromStringAndSize
#undef PyString_FromStringAndSize
@@ -51,7 +50,6 @@ static inline int PyString_AsStringAndSize(PyObject* obj, char** buf,
PyErr_SetString(PyExc_TypeError, "Expecting str or bytes");
return -1;
}
#endif // Py3.3+
template <class T>
inline bool PyObjAs(PyObject* pystr, T* cstr) {
@@ -67,14 +65,12 @@ template <>
inline bool PyObjAs(PyObject* pystr, ::std::string* cstr) {
char* buf;
Py_ssize_t len;
#if PY_VERSION_HEX >= 0x03030000
if (PyUnicode_Check(pystr)) {
buf = PyUnicode_AsUTF8AndSize(pystr, &len);
if (!buf) return false;
} else // NOLINT
#endif
if (PyBytes_AsStringAndSize(pystr, &buf, &len) == -1)
} else if (PyBytes_AsStringAndSize(pystr, &buf, &len) == -1) { // NOLINT
return false;
}
if (cstr) cstr->assign(buf, len);
return true;
}
@@ -83,14 +79,12 @@ template <class T>
inline bool PyObjAs(PyObject* pystr, std::string* cstr) {
char* buf;
Py_ssize_t len;
#if PY_VERSION_HEX >= 0x03030000
if (PyUnicode_Check(pystr)) {
buf = const_cast<char*>(PyUnicode_AsUTF8AndSize(pystr, &len));
if (!buf) return false;
} else // NOLINT
#endif
if (PyBytes_AsStringAndSize(pystr, &buf, &len) == -1)
} else if (PyBytes_AsStringAndSize(pystr, &buf, &len) == -1) { // NOLINT
return false;
}
if (cstr) cstr->assign(buf, len);
return true;
}
@@ -132,36 +126,18 @@ inline bool PyObjAs(PyObject* py, unsigned int* c) {
template <>
inline bool PyObjAs(PyObject* py, int64_t* c) { // NOLINT
int64_t i; // NOLINT
#if PY_MAJOR_VERSION < 3
if (PyInt_Check(py)) {
i = PyInt_AsLong(py);
} else {
if (!PyLong_Check(py)) return false; // Not a Python long.
#else
{
#endif
i = PyLong_AsLongLong(py);
if (i == -1 && PyErr_Occurred()) return false; // Not a C long long.
}
const int64_t i = PyLong_AsLongLong(py);
if (i == -1 && PyErr_Occurred()) return false; // Not a C long long.
if (c) *c = i;
return true;
}
template <>
inline bool PyObjAs(PyObject* py, uint64_t* c) { // NOLINT
uint64_t i; // NOLINT
#if PY_MAJOR_VERSION < 3
if (PyInt_Check(py)) {
i = PyInt_AsUnsignedLongLongMask(py);
} else // NOLINT
#endif
{
if (!PyLong_Check(py)) return false; // Not a Python long.
i = PyLong_AsUnsignedLongLong(py);
if (i == (uint64_t)-1 && PyErr_Occurred()) // NOLINT
return false;
}
if (!PyLong_Check(py)) return false; // Not a Python long.
const uint64_t i = PyLong_AsUnsignedLongLong(py);
if (i == (uint64_t)-1 && PyErr_Occurred()) // NOLINT
return false;
if (c) *c = i;
return true;
}
@@ -171,12 +147,6 @@ inline bool PyObjAs(PyObject* py, double* c) {
double d;
if (PyFloat_Check(py)) {
d = PyFloat_AsDouble(py);
#if PY_MAJOR_VERSION < 3
} else if (PyInt_Check(py)) {
d = PyInt_AsLong(py);
} else if (!PyLong_Check(py)) {
return false; // float or int/long expected
#endif
} else {
d = PyLong_AsDouble(py);
if (d == -1.0 && PyErr_Occurred()) {
@@ -210,13 +180,7 @@ inline bool PyObjAs(PyObject* py, bool* c) {
return true;
}
inline int SwigPyIntOrLong_Check(PyObject* o) {
return (PyLong_Check(o)
#if PY_MAJOR_VERSION <= 2
|| PyInt_Check(o)
#endif
); // NOLINT
}
inline int SwigPyIntOrLong_Check(PyObject* o) { return PyLong_Check(o); }
inline int SwigString_Check(PyObject* o) { return PyUnicode_Check(o); }
@@ -352,13 +316,11 @@ inline PyObject* vector_output_wrap_helper(const std::vector<T*>* vec,
#endif
}
#if PY_MAJOR_VERSION > 2
/* SWIG 2's own C preprocessor macro for this is too strict.
* It requires a (x) parameter which doesn't work for the case where the
* function is being passed by & as a converter into a helper such as
* vector_output_helper above. */
#undef PyInt_FromLong
#define PyInt_FromLong PyLong_FromLong
#endif
#endif // ORTOOLS_BASE_PYTHON_SWIG_H_

View File

@@ -10,12 +10,17 @@ MOI implementation for the CP-Sat solver
mutable struct CPSATOptimizer <: MOI.AbstractOptimizer
model::Union{Nothing,CpModel}
parameters::Union{Nothing,SatParameters}
# Set of constraints in variable indices
variables_constraints::Set{MOI.ConstraintIndex}
constraint_types_present::Set{Tuple{Type,Type}}
# This structure is updated by the optimize! function.
solve_response::Union{Nothing,CpSolverResponse}
function CPSATOptimizer(; name::String = "")
model = CpModel(name = name)
parameters = SatParameters()
variables_constraints = Set{MOI.ConstraintIndex}()
constraint_types_present = Set{Tuple{Type,Type}}()
new(model, parameters, nothing)
end
@@ -32,6 +37,8 @@ end
function MOI.empty!(optimizer::CPSATOptimizer)
optimizer.model = CpModel()
optimizer.variables_constraints = Set{MOI.ConstraintIndex}()
optimizer.constraint_types_present = Set{Tuple{Type,Type}}()
optimizer.solve_response = nothing
return nothing
@@ -203,3 +210,153 @@ function MOI.get(optimizer::CPSATOptimizer, ::Type{MOI.VariableIndex}, name::Str
return nothing
end
"""
Constraint overrides.
"""
function MOI.supports_constraint(
::CPSATOptimizer,
::Type{MOI.VariableIndex},
::Type{<:SCALAR_SET},
)
return true
end
function MOI.add_constraint(
optimizer::CPSATOptimizer,
vi::MOI.VariableIndex,
c::S,
) where {S<:SCALAR_SET}
if S <: MOI.LessThan
throw_if_upper_bound_is_already_set(optimizer, vi, c)
end
if S <: MOI.GreaterThan
throw_if_lower_bound_is_already_set(optimizer, vi, c)
end
if S <: MOI.Interval
throw_if_upper_bound_is_already_set(optimizer, vi, c)
throw_if_lower_bound_is_already_set(optimizer, vi, c)
end
if S <: MOI.EqualTo
throw_if_upper_bound_is_already_set(optimizer, vi, c)
throw_if_lower_bound_is_already_set(optimizer, vi, c)
end
variable_index = vi.value
# retrieve the constraint bounds
lower_bound, upper_bound = bounds(c)
# TODO: (b/452908268) - Update variable's domain instead of creating a new linear constraint.
linear_constraint = CPSatLinearConstraintProto()
push!(linear_constraint.vars, variable_index)
# In this case, we set the coefficient to 1.
push!(linear_constraint.coeffs, 1)
# Update the domain
push!(linear_constraint.domain, lower_bound)
push!(linear_constraint.domain, upper_bound)
constraint = CPSATConstraint()
constraint.name = :linear
constraint.value = linear_constraint
push!(optimizer.model.constraints, constraint)
push!(
optimizer.variables_constraints,
MOI.ConstraintIndex{MOI.VariableIndex,typeof(c)}(variable_index),
)
push!(optimizer.constraint_types_present, (MOI.VariableIndex, typeof(c)))
return MOI.ConstraintIndex{MOI.VariableIndex,typeof(c)}(variable_index)
end
function throw_if_upper_bound_is_already_set(
optimizer::CPSATOptimizer,
vi::MOI.VariableIndex,
c::S,
) where {S<:SCALAR_SET}
# Assumes type consistency across all constraints.
T = typeof(c).parameters[1]
less_than_idx = MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{T}}(vi.value)
interval_idx = MOI.ConstraintIndex{MOI.VariableIndex,MOI.Interval{T}}(vi.value)
equal_to_idx = MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{T}}(vi.value)
if in(less_than_idx, optimizer.variables_constraints)
throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T},S}(vi))
end
if in(interval_idx, optimizer.variables_constraints)
throw(MOI.UpperBoundAlreadySet{MOI.Interval{T},S}(vi))
end
if in(equal_to_idx, optimizer.variables_constraints)
throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T},S}(vi))
end
return nothing
end
function throw_if_lower_bound_is_already_set(
optimizer::CPSATOptimizer,
vi::MOI.VariableIndex,
c::S,
) where {S<:SCALAR_SET}
# Assumes type consistency across all constraints.
T = typeof(c).parameters[1]
greater_than_idx = MOI.ConstraintIndex{typeof(vi),MOI.GreaterThan{T}}(vi.value)
interval_idx = MOI.ConstraintIndex{typeof(vi),MOI.Interval{T}}(vi.value)
equal_to_idx = MOI.ConstraintIndex{typeof(vi),MOI.EqualTo{T}}(vi.value)
if in(greater_than_idx, optimizer.variables_constraints)
throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T},S}(vi))
end
if in(interval_idx, optimizer.variables_constraints)
throw(MOI.LowerBoundAlreadySet{MOI.Interval{T},S}(vi))
end
if in(equal_to_idx, optimizer.variables_constraints)
throw(MOI.LowerBoundAlreadySet{MOI.EqualTo{T},S}(vi))
end
return nothing
end
function MOI.supports_constraint(
::CPSATOptimizer,
::Type{MOI.VariableIndex},
::Type{MOI.ZeroOne},
)
return true
end
function MOI.add_constraint(
optimizer::CPSATOptimizer,
vi::MOI.VariableIndex,
c::MOI.ZeroOne,
)
# Get the int value of the variable index
index = vi.value
# Set the variable bounds to [0, 1] (override the existing domain)
optimizer.model.variables[index].domain = [zero(Int), one(Int)]
# Update the associated metadata.
# TODO: (b/452416646) - Fetch constraint types dynamically at runtime
push!(optimizer.constraint_types_present, (MOI.VariableIndex, MOI.ZeroOne))
push!(
optimizer.variables_constraints,
MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(index),
)
return MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(index)
end

View File

@@ -66,8 +66,8 @@ simple_sat_program()
The interface to the C++ CP-SAT solver is implemented through the
**CpModelBuilder** class described in
*ortools/sat/cp_model.h*. This class is just a helper to fill
in the cp_model protobuf.
*ortools/sat/cp_model.h*. This class is just a helper to
fill in the cp_model protobuf.
Calling Solve() method will return a CpSolverResponse protobuf that contains the
solve status, the values for each variable in the model if solve was successful,

View File

@@ -12,14 +12,10 @@
# limitations under the License.
load("@protobuf//bazel:cc_proto_library.bzl", "cc_proto_library")
load("@protobuf//bazel:java_proto_library.bzl", "java_proto_library")
load("@protobuf//bazel:proto_library.bzl", "proto_library")
load("@protobuf//bazel:py_proto_library.bzl", "py_proto_library")
package(default_visibility = [
"//ortools/math_opt:__subpackages__",
"//ortools/service:__subpackages__",
])
package(default_visibility = ["//ortools/math_opt:__subpackages__"])
proto_library(
name = "optimization_proto",