[CP-SAT] Python: rewrite support for variable numbers of arguments
This commit is contained in:
@@ -46,6 +46,7 @@ rather than for solving specific optimization problems.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
import copy
|
||||
import threading
|
||||
import time
|
||||
from typing import (
|
||||
@@ -182,13 +183,14 @@ _IndexOrSeries = Union[pd.Index, pd.Series]
|
||||
|
||||
|
||||
# Helper functions.
|
||||
def arg_is_boolean(x: Any) -> bool:
|
||||
"""Checks if the x is a boolean."""
|
||||
if isinstance(x, bool):
|
||||
return True
|
||||
if isinstance(x, np.bool_):
|
||||
return True
|
||||
return False
|
||||
def snake_case_to_camel_case(name: str) -> str:
|
||||
"""Converts a snake_case name to camelCase."""
|
||||
words = name.split("_")
|
||||
return (
|
||||
"".join(word.capitalize() for word in words)
|
||||
.replace("2d", "2D")
|
||||
.replace("Xor", "XOr")
|
||||
)
|
||||
|
||||
|
||||
def object_is_a_true_literal(literal: LiteralT) -> bool:
|
||||
@@ -217,32 +219,6 @@ def object_is_a_false_literal(literal: LiteralT) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def expand_exprs_generator_or_tuple(
|
||||
expressions: Union[tuple[LinearExprT, ...], Iterable[LinearExprT]],
|
||||
) -> Union[Iterable[LinearExprT], LinearExprT]:
|
||||
if hasattr(expressions, "__len__"): # Tuple
|
||||
if len(expressions) != 1:
|
||||
return expressions
|
||||
if isinstance(expressions[0], (NumberTypes, LinearExpr)):
|
||||
return expressions
|
||||
# The `expressions` is a tuple. If it contains a single element which is
|
||||
# itself an iterable (e.g. a generator), unpack it.
|
||||
return expressions[0]
|
||||
|
||||
|
||||
def expand_literals_generator_or_tuple(
|
||||
literals: Union[tuple[LiteralT, ...], Iterable[LiteralT]],
|
||||
) -> Union[Iterable[LiteralT], LiteralT]:
|
||||
if hasattr(literals, "__len__"): # Tuple
|
||||
if len(literals) != 1:
|
||||
return literals
|
||||
if isinstance(literals[0], (NumberTypes, bool, cmh.Literal)):
|
||||
return literals
|
||||
# The `expressions` is a tuple. If it contains a single element which is
|
||||
# itself an iterable (e.g. a generator), unpack it.
|
||||
return literals[0]
|
||||
|
||||
|
||||
def _get_index(obj: _IndexOrSeries) -> pd.Index:
|
||||
"""Returns the indices of `obj` as a `pd.Index`."""
|
||||
if isinstance(obj, pd.Series):
|
||||
@@ -287,8 +263,22 @@ class CpModel(cmh.CpBaseModel):
|
||||
* ```add_``` create new constraints and add them to the model.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
cmh.CpBaseModel.__init__(self)
|
||||
def __init__(self, model_proto: Optional[cmh.CpModelProto] = None) -> None:
|
||||
if model_proto is None:
|
||||
cmh.CpBaseModel.__init__(self)
|
||||
else:
|
||||
cmh.CpBaseModel.__init__(self, model_proto)
|
||||
self._add_pre_pep8_methods()
|
||||
|
||||
def _add_pre_pep8_methods(self) -> None:
|
||||
for method_name in dir(self):
|
||||
if callable(getattr(self, method_name)) and (
|
||||
method_name.startswith("add_")
|
||||
or method_name.startswith("new_")
|
||||
or method_name.startswith("clear_")
|
||||
):
|
||||
pre_pep8_name = snake_case_to_camel_case(method_name)
|
||||
setattr(self, pre_pep8_name, getattr(self, method_name))
|
||||
|
||||
# Naming.
|
||||
@property
|
||||
@@ -469,7 +459,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
"Cannot add a linear expression containing floating point"
|
||||
f" coefficients or constants: {type(linear_expr).__name__!r}"
|
||||
)
|
||||
return self.add_bounded_linear_expression_internal(ble)
|
||||
return self._add_bounded_linear_expression(ble)
|
||||
if isinstance(linear_expr, IntegralTypes):
|
||||
if not domain.contains(int(linear_expr)):
|
||||
return self.add_bool_or([]) # Evaluate to false.
|
||||
@@ -493,10 +483,10 @@ class CpModel(cmh.CpBaseModel):
|
||||
TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean.
|
||||
"""
|
||||
if isinstance(ct, BoundedLinearExpression):
|
||||
return self.add_bounded_linear_expression_internal(ct)
|
||||
if ct and arg_is_boolean(ct):
|
||||
return self._add_bounded_linear_expression(ct)
|
||||
if ct and self.is_boolean_value(ct):
|
||||
return self.add_bool_or([True])
|
||||
if not ct and arg_is_boolean(ct):
|
||||
if not ct and self.is_boolean_value(ct):
|
||||
return self.add_bool_or([]) # Evaluate to false.
|
||||
raise TypeError(f"not supported: CpModel.add({type(ct).__name__!r})")
|
||||
|
||||
@@ -519,9 +509,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An instance of the `Constraint` class.
|
||||
"""
|
||||
return self.add_all_different_internal(
|
||||
list(expand_exprs_generator_or_tuple(expressions))
|
||||
)
|
||||
return self._add_all_different(*expressions)
|
||||
|
||||
def add_element(
|
||||
self,
|
||||
@@ -549,7 +537,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
expression: LinearExprT = list(expressions)[int(index)]
|
||||
return self.add(expression == target)
|
||||
|
||||
return self.add_element_internal(index, expressions, target)
|
||||
return self._add_element(index, expressions, target)
|
||||
|
||||
def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
|
||||
"""Adds Circuit(arcs).
|
||||
@@ -575,7 +563,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
"""
|
||||
if not arcs:
|
||||
raise ValueError("add_circuit expects a non-empty array of arcs")
|
||||
return self.add_circuit_internal(arcs)
|
||||
return self._add_circuit(arcs)
|
||||
|
||||
def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
|
||||
"""Adds a multiple circuit constraint, aka the 'VRP' constraint.
|
||||
@@ -603,7 +591,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
"""
|
||||
if not arcs:
|
||||
raise ValueError("add_multiple_circuit expects a non-empty array of arcs")
|
||||
return self.add_routes_internal(arcs)
|
||||
return self._add_routes(arcs)
|
||||
|
||||
def add_allowed_assignments(
|
||||
self,
|
||||
@@ -637,7 +625,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
"add_allowed_assignments expects a non-empty expressions array"
|
||||
)
|
||||
|
||||
return self.add_table_internal(expressions, tuples_list, False)
|
||||
return self._add_table(expressions, tuples_list, False)
|
||||
|
||||
def add_forbidden_assignments(
|
||||
self,
|
||||
@@ -670,7 +658,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
"add_forbidden_assignments expects a non-empty expressions array"
|
||||
)
|
||||
|
||||
return self.add_table_internal(expressions, tuples_list, True)
|
||||
return self._add_table(expressions, tuples_list, True)
|
||||
|
||||
def add_automaton(
|
||||
self,
|
||||
@@ -732,7 +720,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
if not transition_triples:
|
||||
raise ValueError("add_automaton expects some transition triples")
|
||||
|
||||
return self.add_automaton_internal(
|
||||
return self._add_automaton(
|
||||
transition_expressions,
|
||||
starting_state,
|
||||
final_states,
|
||||
@@ -768,7 +756,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
"In the inverse constraint, the two array variables and"
|
||||
" inverse_variables must have the same length."
|
||||
)
|
||||
return self.add_inverse_internal(variables, inverse_variables)
|
||||
return self._add_inverse(variables, inverse_variables)
|
||||
|
||||
def add_reservoir_constraint(
|
||||
self,
|
||||
@@ -812,7 +800,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
ValueError: if min_level > 0
|
||||
"""
|
||||
|
||||
return self.add_reservoir_internal(
|
||||
return self._add_reservoir(
|
||||
times,
|
||||
level_changes,
|
||||
[],
|
||||
@@ -884,7 +872,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
if not times:
|
||||
raise ValueError("Reservoir constraint must have a non-empty times array")
|
||||
|
||||
return self.add_reservoir_internal(
|
||||
return self._add_reservoir(
|
||||
times,
|
||||
level_changes,
|
||||
actives,
|
||||
@@ -912,9 +900,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
|
||||
def add_bool_or(self, *literals):
|
||||
"""Adds `Or(literals) == true`: sum(literals) >= 1."""
|
||||
return self.add_bool_or_internal(
|
||||
list(expand_literals_generator_or_tuple(literals))
|
||||
)
|
||||
return self._add_bool_argument_constraint("or", *literals)
|
||||
|
||||
@overload
|
||||
def add_at_least_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
|
||||
@@ -924,7 +910,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
|
||||
def add_at_least_one(self, *literals):
|
||||
"""Same as `add_bool_or`: `sum(literals) >= 1`."""
|
||||
return self.add_bool_or(*literals)
|
||||
return self._add_bool_argument_constraint("or", *literals)
|
||||
|
||||
@overload
|
||||
def add_at_most_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
|
||||
@@ -934,9 +920,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
|
||||
def add_at_most_one(self, *literals) -> Constraint:
|
||||
"""Adds `AtMostOne(literals)`: `sum(literals) <= 1`."""
|
||||
return self.add_at_most_one_internal(
|
||||
list(expand_literals_generator_or_tuple(literals))
|
||||
)
|
||||
return self._add_bool_argument_constraint("at_most_one", *literals)
|
||||
|
||||
@overload
|
||||
def add_exactly_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
|
||||
@@ -946,9 +930,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
|
||||
def add_exactly_one(self, *literals):
|
||||
"""Adds `ExactlyOne(literals)`: `sum(literals) == 1`."""
|
||||
return self.add_exactly_one_internal(
|
||||
list(expand_literals_generator_or_tuple(literals))
|
||||
)
|
||||
return self._add_bool_argument_constraint("exactly_one", *literals)
|
||||
|
||||
@overload
|
||||
def add_bool_and(self, literals: Iterable[LiteralT]) -> Constraint: ...
|
||||
@@ -958,9 +940,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
|
||||
def add_bool_and(self, *literals):
|
||||
"""Adds `And(literals) == true`."""
|
||||
return self.add_bool_and_internal(
|
||||
list(expand_literals_generator_or_tuple(literals))
|
||||
)
|
||||
return self._add_bool_argument_constraint("and", *literals)
|
||||
|
||||
@overload
|
||||
def add_bool_xor(self, literals: Iterable[LiteralT]) -> Constraint: ...
|
||||
@@ -980,9 +960,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An `Constraint` object.
|
||||
"""
|
||||
return self.add_bool_xor_internal(
|
||||
list(expand_literals_generator_or_tuple(literals))
|
||||
)
|
||||
return self._add_bool_argument_constraint("xor", *literals)
|
||||
|
||||
@overload
|
||||
def add_min_equality(
|
||||
@@ -996,11 +974,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
|
||||
def add_min_equality(self, target, *expressions) -> Constraint:
|
||||
"""Adds `target == Min(expressions)`."""
|
||||
return self.add_linear_argument_constraint_internal(
|
||||
"min",
|
||||
target,
|
||||
list(expand_exprs_generator_or_tuple(expressions)),
|
||||
)
|
||||
return self._add_linear_argument_constraint("min", target, *expressions)
|
||||
|
||||
@overload
|
||||
def add_max_equality(
|
||||
@@ -1014,23 +988,17 @@ class CpModel(cmh.CpBaseModel):
|
||||
|
||||
def add_max_equality(self, target, *expressions) -> Constraint:
|
||||
"""Adds `target == Max(expressions)`."""
|
||||
return self.add_linear_argument_constraint_internal(
|
||||
"max",
|
||||
target,
|
||||
list(expand_exprs_generator_or_tuple(expressions)),
|
||||
)
|
||||
return self._add_linear_argument_constraint("max", target, *expressions)
|
||||
|
||||
def add_division_equality(
|
||||
self, target: LinearExprT, num: LinearExprT, denom: LinearExprT
|
||||
) -> Constraint:
|
||||
"""Adds `target == num // denom` (integer division rounded towards 0)."""
|
||||
return self.add_linear_argument_constraint_internal("div", target, [num, denom])
|
||||
return self._add_linear_argument_constraint("div", target, [num, denom])
|
||||
|
||||
def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint:
|
||||
"""Adds `target == Abs(expr)`."""
|
||||
return self.add_linear_argument_constraint_internal(
|
||||
"max", target, [expr, -expr]
|
||||
)
|
||||
return self._add_linear_argument_constraint("max", target, [expr, -expr])
|
||||
|
||||
def add_modulo_equality(
|
||||
self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT
|
||||
@@ -1054,7 +1022,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
A `Constraint` object.
|
||||
"""
|
||||
return self.add_linear_argument_constraint_internal("mod", target, [expr, mod])
|
||||
return self._add_linear_argument_constraint("mod", target, [expr, mod])
|
||||
|
||||
def add_multiplication_equality(
|
||||
self,
|
||||
@@ -1062,13 +1030,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
*expressions: Union[Iterable[LinearExprT], LinearExprT],
|
||||
) -> Constraint:
|
||||
"""Adds `target == expressions[0] * .. * expressions[n]`."""
|
||||
return (
|
||||
self.add_linear_argument_constraint_internal(
|
||||
"prod",
|
||||
target,
|
||||
list(expand_exprs_generator_or_tuple(expressions)),
|
||||
),
|
||||
)
|
||||
return self._add_linear_argument_constraint("prod", target, *expressions)
|
||||
|
||||
# Scheduling support
|
||||
|
||||
@@ -1091,7 +1053,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An `IntervalVar` object.
|
||||
"""
|
||||
return self.new_interval_var_internal(name, start, size, end, [])
|
||||
return self._new_interval_var(name, start, size, end, [])
|
||||
|
||||
def new_interval_var_series(
|
||||
self,
|
||||
@@ -1161,7 +1123,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An `IntervalVar` object.
|
||||
"""
|
||||
return self.new_interval_var_internal(name, start, size, start + size, [])
|
||||
return self._new_interval_var(name, start, size, start + size, [])
|
||||
|
||||
def new_fixed_size_interval_var_series(
|
||||
self,
|
||||
@@ -1237,7 +1199,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An `IntervalVar` object.
|
||||
"""
|
||||
return self.new_interval_var_internal(
|
||||
return self._new_interval_var(
|
||||
name,
|
||||
start,
|
||||
size,
|
||||
@@ -1326,7 +1288,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An `IntervalVar` object.
|
||||
"""
|
||||
return self.new_interval_var_internal(
|
||||
return self._new_interval_var(
|
||||
name,
|
||||
start,
|
||||
size,
|
||||
@@ -1398,7 +1360,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An instance of the `Constraint` class.
|
||||
"""
|
||||
return self.add_no_overlap_internal(intervals)
|
||||
return self._add_no_overlap(intervals)
|
||||
|
||||
def add_no_overlap_2d(
|
||||
self,
|
||||
@@ -1421,7 +1383,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An instance of the `Constraint` class.
|
||||
"""
|
||||
return self.add_no_overlap_2d_internal(x_intervals, y_intervals)
|
||||
return self._add_no_overlap_2d(x_intervals, y_intervals)
|
||||
|
||||
def add_cumulative(
|
||||
self,
|
||||
@@ -1448,7 +1410,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
Returns:
|
||||
An instance of the `Constraint` class.
|
||||
"""
|
||||
return self.add_cumulative_internal(intervals, demands, capacity)
|
||||
return self._add_cumulative(intervals, demands, capacity)
|
||||
|
||||
# Support for model cloning.
|
||||
def clone(self) -> "CpModel":
|
||||
@@ -1458,6 +1420,12 @@ class CpModel(cmh.CpBaseModel):
|
||||
clone.rebuild_constant_map()
|
||||
return clone
|
||||
|
||||
def __copy__(self):
|
||||
return CpModel(self.model_proto)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return CpModel(copy.deepcopy(self.model_proto, memo))
|
||||
|
||||
def get_bool_var_from_proto_index(self, index: int) -> IntVar:
|
||||
"""Returns an already created Boolean variable from its index."""
|
||||
if index < 0 or index >= len(self.model_proto.variables):
|
||||
@@ -1662,48 +1630,7 @@ class CpModel(cmh.CpBaseModel):
|
||||
def Proto(self) -> cmh.CpModelProto:
|
||||
return self.proto
|
||||
|
||||
NewIntVar = new_int_var
|
||||
NewIntVarFromDomain = new_int_var_from_domain
|
||||
NewBoolVar = new_bool_var
|
||||
NewConstant = new_constant
|
||||
NewIntVarSeries = new_int_var_series
|
||||
NewBoolVarSeries = new_bool_var_series
|
||||
AddLinearConstraint = add_linear_constraint
|
||||
AddLinearExpressionInDomain = add_linear_expression_in_domain
|
||||
Add = add
|
||||
AddAllDifferent = add_all_different
|
||||
AddElement = add_element
|
||||
AddCircuit = add_circuit
|
||||
AddMultipleCircuit = add_multiple_circuit
|
||||
AddAllowedAssignments = add_allowed_assignments
|
||||
AddForbiddenAssignments = add_forbidden_assignments
|
||||
AddAutomaton = add_automaton
|
||||
AddInverse = add_inverse
|
||||
AddReservoirConstraint = add_reservoir_constraint
|
||||
AddReservoirConstraintWithActive = add_reservoir_constraint_with_active
|
||||
AddImplication = add_implication
|
||||
AddBoolOr = add_bool_or
|
||||
AddAtLeastOne = add_at_least_one
|
||||
AddAtMostOne = add_at_most_one
|
||||
AddExactlyOne = add_exactly_one
|
||||
AddBoolAnd = add_bool_and
|
||||
AddBoolXOr = add_bool_xor
|
||||
AddMinEquality = add_min_equality
|
||||
AddMaxEquality = add_max_equality
|
||||
AddDivisionEquality = add_division_equality
|
||||
AddAbsEquality = add_abs_equality
|
||||
AddModuloEquality = add_modulo_equality
|
||||
AddMultiplicationEquality = add_multiplication_equality
|
||||
NewIntervalVar = new_interval_var
|
||||
NewIntervalVarSeries = new_interval_var_series
|
||||
NewFixedSizeIntervalVar = new_fixed_size_interval_var
|
||||
NewOptionalIntervalVar = new_optional_interval_var
|
||||
NewOptionalIntervalVarSeries = new_optional_interval_var_series
|
||||
NewOptionalFixedSizeIntervalVar = new_optional_fixed_size_interval_var
|
||||
NewOptionalFixedSizeIntervalVarSeries = new_optional_fixed_size_interval_var_series
|
||||
AddNoOverlap = add_no_overlap
|
||||
AddNoOverlap2D = add_no_overlap_2d
|
||||
AddCumulative = add_cumulative
|
||||
Clone = clone
|
||||
GetBoolVarFromProtoIndex = get_bool_var_from_proto_index
|
||||
GetIntVarFromProtoIndex = get_int_var_from_proto_index
|
||||
@@ -1711,16 +1638,12 @@ class CpModel(cmh.CpBaseModel):
|
||||
Minimize = minimize
|
||||
Maximize = maximize
|
||||
HasObjective = has_objective
|
||||
ClearObjective = clear_objective
|
||||
AddDecisionStrategy = add_decision_strategy
|
||||
ModelStats = model_stats
|
||||
Validate = validate
|
||||
ExportToFile = export_to_file
|
||||
AddHint = add_hint
|
||||
ClearHints = clear_hints
|
||||
AddAssumption = add_assumption
|
||||
AddAssumptions = add_assumptions
|
||||
ClearAssumptions = clear_assumptions
|
||||
|
||||
# add_XXX, new_XXX, and clear_XXX methods are already duplicated
|
||||
# automatically.
|
||||
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
@@ -2092,10 +2015,13 @@ class CpSolverSolutionCallback(cmh.SolutionCallback):
|
||||
def __init__(self) -> None:
|
||||
cmh.SolutionCallback.__init__(self)
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def OnSolutionCallback(self) -> None:
|
||||
"""Proxy for the same method in snake case."""
|
||||
self.on_solution_callback()
|
||||
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
def boolean_value(self, lit: LiteralT) -> bool:
|
||||
"""Returns the boolean value of a boolean literal.
|
||||
|
||||
|
||||
@@ -464,9 +464,12 @@ class IntervalVar;
|
||||
|
||||
class CpBaseModel : public std::enable_shared_from_this<CpBaseModel> {
|
||||
public:
|
||||
CpBaseModel() : model_proto_(std::make_shared<CpModelProto>()) {}
|
||||
CpBaseModel()
|
||||
: model_proto_(std::make_shared<CpModelProto>()),
|
||||
numpy_bool_type_(py::dtype::of<bool>().attr("type").cast<py::type>()) {}
|
||||
explicit CpBaseModel(std::shared_ptr<CpModelProto> model_proto)
|
||||
: model_proto_(model_proto) {}
|
||||
: model_proto_(model_proto),
|
||||
numpy_bool_type_(py::dtype::of<bool>().attr("type").cast<py::type>()) {}
|
||||
|
||||
std::shared_ptr<CpModelProto> model_proto() const { return model_proto_; }
|
||||
|
||||
@@ -501,9 +504,7 @@ class CpBaseModel : public std::enable_shared_from_this<CpBaseModel> {
|
||||
literal.cast<std::shared_ptr<NotBooleanVariable>>();
|
||||
AssertVariableIsBoolean(not_var);
|
||||
return not_var->index();
|
||||
} else if (py::isinstance<py::bool_>(literal) ||
|
||||
py::type::of(literal).attr("__name__").cast<std::string>() ==
|
||||
"bool") {
|
||||
} else if (IsBooleanValue(literal)) {
|
||||
const bool value = literal.cast<py::bool_>();
|
||||
if (value) {
|
||||
return GetOrMakeIndexFromConstant(1);
|
||||
@@ -559,20 +560,20 @@ class CpBaseModel : public std::enable_shared_from_this<CpBaseModel> {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> AddAllDifferentInternal(py::sequence exprs);
|
||||
bool IsBooleanValue(py::handle value) {
|
||||
return py::isinstance<py::bool_>(value) ||
|
||||
py::isinstance(value, numpy_bool_type_);
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> AddAtMostOneInternal(py::sequence literals);
|
||||
std::shared_ptr<Constraint> AddAllDifferentInternal(py::args exprs);
|
||||
|
||||
std::shared_ptr<Constraint> AddAutomatonInternal(
|
||||
py::sequence transition_expressions, int64_t starting_state,
|
||||
const std::vector<int64_t>& final_states,
|
||||
const std::vector<std::vector<int64_t>>& transition_triples);
|
||||
|
||||
std::shared_ptr<Constraint> AddBoolAndInternal(py::sequence literals);
|
||||
|
||||
std::shared_ptr<Constraint> AddBoolOrInternal(py::sequence literals);
|
||||
|
||||
std::shared_ptr<Constraint> AddBoolXOrInternal(py::sequence literals);
|
||||
std::shared_ptr<Constraint> AddBoolArgumentConstraintInternal(
|
||||
const std::string& name, py::args literals);
|
||||
|
||||
std::shared_ptr<Constraint> AddBoundedLinearExpressionInternal(
|
||||
BoundedLinearExpression* ble);
|
||||
@@ -581,13 +582,11 @@ class CpBaseModel : public std::enable_shared_from_this<CpBaseModel> {
|
||||
py::sequence exprs,
|
||||
const py::handle& target);
|
||||
|
||||
std::shared_ptr<Constraint> AddExactlyOneInternal(py::sequence literals);
|
||||
|
||||
std::shared_ptr<Constraint> AddInverseInternal(py::sequence direct,
|
||||
py::sequence inverse);
|
||||
|
||||
std::shared_ptr<Constraint> AddLinearArgumentConstraintInternal(
|
||||
const std::string& name, const py::handle& target, py::sequence exprs);
|
||||
const std::string& name, const py::handle& target, py::args exprs);
|
||||
|
||||
std::shared_ptr<Constraint> AddReservoirInternal(py::sequence times,
|
||||
py::sequence level_changes,
|
||||
@@ -625,6 +624,7 @@ class CpBaseModel : public std::enable_shared_from_this<CpBaseModel> {
|
||||
private:
|
||||
std::shared_ptr<CpModelProto> model_proto_;
|
||||
absl::flat_hash_map<int64_t, int> cache_;
|
||||
py::type numpy_bool_type_;
|
||||
};
|
||||
|
||||
class Constraint {
|
||||
@@ -661,22 +661,17 @@ class Constraint {
|
||||
};
|
||||
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddAllDifferentInternal(
|
||||
py::sequence exprs) {
|
||||
py::args exprs) {
|
||||
const int ct_index = model_proto_->constraints_size();
|
||||
ConstraintProto* ct = model_proto_->add_constraints();
|
||||
for (const auto& expr : exprs) {
|
||||
LinearExprToProto(expr, 1, ct->mutable_all_diff()->add_exprs());
|
||||
}
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddAtMostOneInternal(
|
||||
py::sequence literals) {
|
||||
const int ct_index = model_proto_->constraints_size();
|
||||
ConstraintProto* ct = model_proto_->add_constraints();
|
||||
ct->mutable_at_most_one(); // If literals is empty.
|
||||
for (const auto& literal : literals) {
|
||||
ct->mutable_at_most_one()->add_literals(GetOrMakeBooleanIndex(literal));
|
||||
if (exprs.size() == 1 && py::isinstance<py::iterable>(exprs[0])) {
|
||||
for (const auto& expr : exprs[0]) {
|
||||
LinearExprToProto(expr, 1, ct->mutable_all_diff()->add_exprs());
|
||||
}
|
||||
} else {
|
||||
for (const auto& expr : exprs) {
|
||||
LinearExprToProto(expr, 1, ct->mutable_all_diff()->add_exprs());
|
||||
}
|
||||
}
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
}
|
||||
@@ -706,35 +701,33 @@ std::shared_ptr<Constraint> CpBaseModel::AddAutomatonInternal(
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddBoolAndInternal(
|
||||
py::sequence literals) {
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddBoolArgumentConstraintInternal(
|
||||
const std::string& name, py::args literals) {
|
||||
const int ct_index = model_proto_->constraints_size();
|
||||
ConstraintProto* ct = model_proto_->add_constraints();
|
||||
ct->mutable_bool_and(); // If literals is empty.
|
||||
for (const auto& literal : literals) {
|
||||
ct->mutable_bool_and()->add_literals(GetOrMakeBooleanIndex(literal));
|
||||
BoolArgumentProto* proto = nullptr;
|
||||
if (name == "or") {
|
||||
proto = ct->mutable_bool_or();
|
||||
} else if (name == "and") {
|
||||
proto = ct->mutable_bool_and();
|
||||
} else if (name == "xor") {
|
||||
proto = ct->mutable_bool_xor();
|
||||
} else if (name == "at_most_one") {
|
||||
proto = ct->mutable_at_most_one();
|
||||
} else if (name == "exactly_one") {
|
||||
proto = ct->mutable_exactly_one();
|
||||
} else {
|
||||
ThrowError(PyExc_ValueError,
|
||||
absl::StrCat("Unknown boolean argument constraint: ", name));
|
||||
}
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddBoolOrInternal(
|
||||
py::sequence literals) {
|
||||
const int ct_index = model_proto_->constraints_size();
|
||||
ConstraintProto* ct = model_proto_->add_constraints();
|
||||
ct->mutable_bool_or(); // If literals is empty.
|
||||
for (const auto& literal : literals) {
|
||||
ct->mutable_bool_or()->add_literals(GetOrMakeBooleanIndex(literal));
|
||||
}
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddBoolXOrInternal(
|
||||
py::sequence literals) {
|
||||
const int ct_index = model_proto_->constraints_size();
|
||||
ConstraintProto* ct = model_proto_->add_constraints();
|
||||
ct->mutable_bool_xor(); // If literals is empty.
|
||||
for (const auto& literal : literals) {
|
||||
ct->mutable_bool_xor()->add_literals(GetOrMakeBooleanIndex(literal));
|
||||
if (literals.size() == 1 && py::isinstance<py::iterable>(literals[0])) {
|
||||
for (const auto& literal : literals[0]) {
|
||||
proto->add_literals(GetOrMakeBooleanIndex(literal));
|
||||
}
|
||||
} else {
|
||||
for (const auto& literal : literals) {
|
||||
proto->add_literals(GetOrMakeBooleanIndex(literal));
|
||||
}
|
||||
}
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
}
|
||||
@@ -774,17 +767,6 @@ std::shared_ptr<Constraint> CpBaseModel::AddElementInternal(
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddExactlyOneInternal(
|
||||
py::sequence literals) {
|
||||
const int ct_index = model_proto_->constraints_size();
|
||||
ConstraintProto* ct = model_proto_->add_constraints();
|
||||
ct->mutable_exactly_one(); // If literals is empty.
|
||||
for (const auto& literal : literals) {
|
||||
ct->mutable_exactly_one()->add_literals(GetOrMakeBooleanIndex(literal));
|
||||
}
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddInverseInternal(
|
||||
py::sequence direct, py::sequence inverse) {
|
||||
const int ct_index = model_proto_->constraints_size();
|
||||
@@ -799,7 +781,7 @@ std::shared_ptr<Constraint> CpBaseModel::AddInverseInternal(
|
||||
}
|
||||
|
||||
std::shared_ptr<Constraint> CpBaseModel::AddLinearArgumentConstraintInternal(
|
||||
const std::string& name, const py::handle& target, py::sequence exprs) {
|
||||
const std::string& name, const py::handle& target, py::args exprs) {
|
||||
const int ct_index = model_proto_->constraints_size();
|
||||
ConstraintProto* ct = model_proto_->add_constraints();
|
||||
LinearArgumentProto* proto;
|
||||
@@ -821,8 +803,15 @@ std::shared_ptr<Constraint> CpBaseModel::AddLinearArgumentConstraintInternal(
|
||||
}
|
||||
|
||||
LinearExprToProto(target, multiplier, proto->mutable_target());
|
||||
for (const auto& expr : exprs) {
|
||||
LinearExprToProto(expr, multiplier, proto->add_exprs());
|
||||
|
||||
if (exprs.size() == 1 && py::isinstance<py::iterable>(exprs[0])) {
|
||||
for (const auto& expr : exprs[0]) {
|
||||
LinearExprToProto(expr, multiplier, proto->add_exprs());
|
||||
}
|
||||
} else {
|
||||
for (const auto& expr : exprs) {
|
||||
LinearExprToProto(expr, multiplier, proto->add_exprs());
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<Constraint>(shared_from_this(), ct_index);
|
||||
@@ -1885,6 +1874,7 @@ PYBIND11_MODULE(cp_model_helper, m) {
|
||||
py::class_<CpBaseModel, std::shared_ptr<CpBaseModel>>(
|
||||
m, "CpBaseModel", "Base class for the CP model.")
|
||||
.def(py::init<>())
|
||||
.def(py::init<std::shared_ptr<CpModelProto>>())
|
||||
.def_property_readonly("model_proto", &CpBaseModel::model_proto,
|
||||
"Returns the CP model protobuf")
|
||||
.def("get_or_make_index_from_constant",
|
||||
@@ -1895,68 +1885,42 @@ PYBIND11_MODULE(cp_model_helper, m) {
|
||||
.def("get_or_make_variable_index", &CpBaseModel::GetOrMakeVariableIndex,
|
||||
py::arg("arg"),
|
||||
"Returns the index of the given variable or constant variable.")
|
||||
.def("is_boolean_value", &CpBaseModel::IsBooleanValue, py::arg("value"))
|
||||
.def("rebuild_constant_map", &CpBaseModel::RebuildConstantMap)
|
||||
.def("add_all_different_internal", &CpBaseModel::AddAllDifferentInternal,
|
||||
py::arg("expressions"))
|
||||
.def("add_at_most_one_internal", &CpBaseModel::AddAtMostOneInternal,
|
||||
py::arg("literals"))
|
||||
.def("add_automaton_internal", &CpBaseModel::AddAutomatonInternal,
|
||||
.def("_add_all_different", &CpBaseModel::AddAllDifferentInternal)
|
||||
.def("_add_automaton", &CpBaseModel::AddAutomatonInternal,
|
||||
py::arg("transition_expressions"), py::arg("starting_state"),
|
||||
py::arg("final_states"), py::arg("transition_triples"))
|
||||
.def("add_bool_and_internal", &CpBaseModel::AddBoolAndInternal,
|
||||
py::arg("literals"))
|
||||
.def("add_bool_or_internal", &CpBaseModel::AddBoolOrInternal,
|
||||
py::arg("literals"))
|
||||
.def("add_bool_xor_internal", &CpBaseModel::AddBoolXOrInternal,
|
||||
py::arg("literals"))
|
||||
.def("add_bounded_linear_expression_internal",
|
||||
.def("_add_bool_argument_constraint",
|
||||
&CpBaseModel::AddBoolArgumentConstraintInternal, py::arg("name"))
|
||||
.def("_add_bounded_linear_expression",
|
||||
&CpBaseModel::AddBoundedLinearExpressionInternal, py::arg("ble"))
|
||||
.def("add_element_internal", &CpBaseModel::AddElementInternal,
|
||||
.def("_add_element", &CpBaseModel::AddElementInternal,
|
||||
py::arg("index").none(false), py::arg("expressions"),
|
||||
py::arg("target").none(false))
|
||||
.def("add_exactly_one_internal", &CpBaseModel::AddExactlyOneInternal,
|
||||
py::arg("literals"))
|
||||
.def("add_linear_argument_constraint_internal",
|
||||
.def("_add_linear_argument_constraint",
|
||||
&CpBaseModel::AddLinearArgumentConstraintInternal,
|
||||
py::arg("name").none(false), py::arg("target").none(false),
|
||||
py::arg("exprs"))
|
||||
.def("add_inverse_internal", &CpBaseModel::AddInverseInternal,
|
||||
py::arg("direct"), py::arg("inverse"))
|
||||
.def("add_reservoir_internal", &CpBaseModel::AddReservoirInternal,
|
||||
py::arg("name").none(false), py::arg("target").none(false))
|
||||
.def("_add_inverse", &CpBaseModel::AddInverseInternal, py::arg("direct"),
|
||||
py::arg("inverse"))
|
||||
.def("_add_reservoir", &CpBaseModel::AddReservoirInternal,
|
||||
py::arg("times"), py::arg("level_changes"), py::arg("actives"),
|
||||
py::arg("min_level"), py::arg("max_level"))
|
||||
.def("add_table_internal", &CpBaseModel::AddTableInternal,
|
||||
py::arg("expressions"), py::arg("values"), py::arg("negated"))
|
||||
.def("_add_table", &CpBaseModel::AddTableInternal, py::arg("expressions"),
|
||||
py::arg("values"), py::arg("negated"))
|
||||
// Scheduling support.
|
||||
.def("new_interval_var_internal", &CpBaseModel::NewIntervalVarInternal,
|
||||
.def("_new_interval_var", &CpBaseModel::NewIntervalVarInternal,
|
||||
py::arg("name"), py::arg("start"), py::arg("size"), py::arg("end"),
|
||||
py::arg("Literals"))
|
||||
.def("add_no_overlap_internal", &CpBaseModel::AddNoOverlapInternal,
|
||||
.def("_add_no_overlap", &CpBaseModel::AddNoOverlapInternal,
|
||||
py::arg("intervals"))
|
||||
.def("add_no_overlap_2d_internal", &CpBaseModel::AddNoOverlap2DInternal,
|
||||
.def("_add_no_overlap_2d", &CpBaseModel::AddNoOverlap2DInternal,
|
||||
py::arg("x_intervals"), py::arg("y_intervals"))
|
||||
.def("add_cumulative_internal", &CpBaseModel::AddCumulativeInternal,
|
||||
.def("_add_cumulative", &CpBaseModel::AddCumulativeInternal,
|
||||
py::arg("intervals"), py::arg("demands"), py::arg("capacity"))
|
||||
// Routing support.
|
||||
.def("add_circuit_internal", &CpBaseModel::AddCircuitInternal,
|
||||
py::arg("arcs"))
|
||||
.def("add_routes_internal", &CpBaseModel::AddRoutesInternal,
|
||||
py::arg("arcs"))
|
||||
// Misc support
|
||||
.def(py::pickle(
|
||||
[](std::shared_ptr<CpBaseModel> p) { // __getstate__
|
||||
/* Return a tuple that fully encodes the state of the object */
|
||||
return py::make_tuple(p->model_proto());
|
||||
},
|
||||
[](py::tuple t) { // __setstate__
|
||||
if (t.size() != 1) throw std::runtime_error("Invalid state!");
|
||||
std::shared_ptr<CpModelProto> model_proto =
|
||||
t[0].cast<std::shared_ptr<CpModelProto>>();
|
||||
if (model_proto == nullptr) {
|
||||
throw std::runtime_error("Invalid state!");
|
||||
}
|
||||
return std::make_shared<CpBaseModel>(model_proto);
|
||||
}));
|
||||
.def("_add_circuit", &CpBaseModel::AddCircuitInternal, py::arg("arcs"))
|
||||
.def("_add_routes", &CpBaseModel::AddRoutesInternal, py::arg("arcs"));
|
||||
|
||||
static const char* kConstraintDoc = R"doc(
|
||||
Base class for constraints.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import itertools
|
||||
import sys
|
||||
@@ -184,12 +185,13 @@ class CpModelTest(absltest.TestCase):
|
||||
sys.stdout.flush()
|
||||
|
||||
def test_is_boolean(self):
|
||||
self.assertTrue(cp_model.arg_is_boolean(True))
|
||||
self.assertTrue(cp_model.arg_is_boolean(False))
|
||||
self.assertFalse(cp_model.arg_is_boolean(1))
|
||||
self.assertFalse(cp_model.arg_is_boolean(0))
|
||||
self.assertTrue(cp_model.arg_is_boolean(np.bool_(1)))
|
||||
self.assertTrue(cp_model.arg_is_boolean(np.bool_(0)))
|
||||
model = cp_model.CpModel()
|
||||
self.assertTrue(model.is_boolean_value(True))
|
||||
self.assertTrue(model.is_boolean_value(False))
|
||||
self.assertFalse(model.is_boolean_value(1))
|
||||
self.assertFalse(model.is_boolean_value(0))
|
||||
self.assertTrue(model.is_boolean_value(np.bool_(1)))
|
||||
self.assertTrue(model.is_boolean_value(np.bool_(0)))
|
||||
|
||||
def test_create_integer_variable(self) -> None:
|
||||
model = cp_model.CpModel()
|
||||
@@ -220,6 +222,12 @@ class CpModelTest(absltest.TestCase):
|
||||
variables = set()
|
||||
variables.add(var_a)
|
||||
|
||||
accumulator = collections.defaultdict(int)
|
||||
accumulator[var_a] += 1
|
||||
self.assertEqual(accumulator[var_a], 1)
|
||||
accumulator[model.get_int_var_from_proto_index(var_a.index)] += 3
|
||||
self.assertEqual(accumulator[var_a], 4)
|
||||
|
||||
def test_literal(self) -> None:
|
||||
model = cp_model.CpModel()
|
||||
x = model.new_bool_var("x")
|
||||
@@ -934,6 +942,7 @@ class CpModelTest(absltest.TestCase):
|
||||
self.assertLen(model.proto.constraints, 1)
|
||||
self.assertLen(model.proto.constraints[0].table.exprs, 5)
|
||||
self.assertLen(model.proto.constraints[0].table.values, 15)
|
||||
self.assertFalse(model.proto.constraints[0].table.negated)
|
||||
with self.assertRaises(ValueError):
|
||||
model.add_allowed_assignments(
|
||||
x,
|
||||
@@ -1293,12 +1302,14 @@ class CpModelTest(absltest.TestCase):
|
||||
model.add_bool_or(True, x[0], x[2])
|
||||
model.add_bool_or(False, x[0])
|
||||
model.add_bool_or(x[i] for i in [0, 2, 3, 4])
|
||||
model.add_bool_or(x[3])
|
||||
self.assertLen(model.proto.variables, 7)
|
||||
self.assertLen(model.proto.constraints, 4)
|
||||
self.assertLen(model.proto.constraints, 5)
|
||||
self.assertLen(model.proto.constraints[0].bool_or.literals, 5)
|
||||
self.assertLen(model.proto.constraints[1].bool_or.literals, 3)
|
||||
self.assertLen(model.proto.constraints[2].bool_or.literals, 2)
|
||||
self.assertLen(model.proto.constraints[3].bool_or.literals, 4)
|
||||
self.assertLen(model.proto.constraints[4].bool_or.literals, 1)
|
||||
|
||||
def test_at_least_one(self) -> None:
|
||||
model = cp_model.CpModel()
|
||||
@@ -2672,6 +2683,24 @@ TRFM"""
|
||||
self.assertEqual(-2, prod.coefficient)
|
||||
self.assertEqual(2, prod.offset)
|
||||
|
||||
def test_pre_pep8(self):
|
||||
model = cp_model.CpModel()
|
||||
x = [model.NewBoolVar(f"x{i}") for i in range(5)]
|
||||
model.AddBoolOr(x)
|
||||
self.assertLen(model.proto.variables, 5)
|
||||
self.assertLen(model.proto.constraints, 1)
|
||||
self.assertLen(model.proto.constraints[0].bool_or.literals, 5)
|
||||
|
||||
model_copy = copy.copy(model)
|
||||
self.assertTrue(hasattr(model_copy, "AddBoolOr"))
|
||||
self.assertTrue(hasattr(model_copy, "AddBoolXOr"))
|
||||
self.assertTrue(hasattr(model_copy, "AddNoOverlap2D"))
|
||||
|
||||
model_deepcopy = copy.deepcopy(model)
|
||||
self.assertTrue(hasattr(model_deepcopy, "AddBoolOr"))
|
||||
self.assertTrue(hasattr(model_deepcopy, "AddBoolXOr"))
|
||||
self.assertTrue(hasattr(model_deepcopy, "AddNoOverlap2D"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
absltest.main()
|
||||
|
||||
Reference in New Issue
Block a user