14"""Methods for building and solving CP-SAT models.
16The following two sections describe the main
17methods for building and solving CP-SAT models.
19* [`CpModel`](#cp_model.CpModel): Methods for creating
20models, including variables and constraints.
21* [`CPSolver`](#cp_model.CpSolver): Methods for solving
22a model and evaluating solutions.
24The following methods implement callbacks that the
25solver calls each time it finds a new solution.
27* [`CpSolverSolutionCallback`](#cp_model.CpSolverSolutionCallback):
28 A general method for implementing callbacks.
29* [`ObjectiveSolutionPrinter`](#cp_model.ObjectiveSolutionPrinter):
30 Print objective values and elapsed time for intermediate solutions.
31* [`VarArraySolutionPrinter`](#cp_model.VarArraySolutionPrinter):
32 Print intermediate solutions (variable values, time).
33* [`VarArrayAndObjectiveSolutionPrinter`]
34 (#cp_model.VarArrayAndObjectiveSolutionPrinter):
35 Print both intermediate solutions and objective values.
37Additional methods for solving CP-SAT models:
39* [`Constraint`](#cp_model.Constraint): A few utility methods for modifying
40 constraints created by `CpModel`.
41* [`LinearExpr`](#lineacp_model.LinearExpr): Methods for creating constraints
42 and the objective from large arrays of coefficients.
44Other methods and functions listed are primarily used for developing OR-Tools,
45rather than for solving specific optimization problems.
74Domain = sorted_interval_list.Domain
75BoundedLinearExpression = cmh.BoundedLinearExpression
76FlatFloatExpr = cmh.FlatFloatExpr
77FlatIntExpr = cmh.FlatIntExpr
78LinearExpr = cmh.LinearExpr
79NotBooleanVariable = cmh.NotBooleanVariable
92UNKNOWN = cp_model_pb2.UNKNOWN
93MODEL_INVALID = cp_model_pb2.MODEL_INVALID
94FEASIBLE = cp_model_pb2.FEASIBLE
95INFEASIBLE = cp_model_pb2.INFEASIBLE
96OPTIMAL = cp_model_pb2.OPTIMAL
99CHOOSE_FIRST = cp_model_pb2.DecisionStrategyProto.CHOOSE_FIRST
100CHOOSE_LOWEST_MIN = cp_model_pb2.DecisionStrategyProto.CHOOSE_LOWEST_MIN
101CHOOSE_HIGHEST_MAX = cp_model_pb2.DecisionStrategyProto.CHOOSE_HIGHEST_MAX
102CHOOSE_MIN_DOMAIN_SIZE = cp_model_pb2.DecisionStrategyProto.CHOOSE_MIN_DOMAIN_SIZE
103CHOOSE_MAX_DOMAIN_SIZE = cp_model_pb2.DecisionStrategyProto.CHOOSE_MAX_DOMAIN_SIZE
106SELECT_MIN_VALUE = cp_model_pb2.DecisionStrategyProto.SELECT_MIN_VALUE
107SELECT_MAX_VALUE = cp_model_pb2.DecisionStrategyProto.SELECT_MAX_VALUE
108SELECT_LOWER_HALF = cp_model_pb2.DecisionStrategyProto.SELECT_LOWER_HALF
109SELECT_UPPER_HALF = cp_model_pb2.DecisionStrategyProto.SELECT_UPPER_HALF
110SELECT_MEDIAN_VALUE = cp_model_pb2.DecisionStrategyProto.SELECT_MEDIAN_VALUE
111SELECT_RANDOM_HALF = cp_model_pb2.DecisionStrategyProto.SELECT_RANDOM_HALF
114AUTOMATIC_SEARCH = sat_parameters_pb2.SatParameters.AUTOMATIC_SEARCH
115FIXED_SEARCH = sat_parameters_pb2.SatParameters.FIXED_SEARCH
116PORTFOLIO_SEARCH = sat_parameters_pb2.SatParameters.PORTFOLIO_SEARCH
117LP_SEARCH = sat_parameters_pb2.SatParameters.LP_SEARCH
118PSEUDO_COST_SEARCH = sat_parameters_pb2.SatParameters.PSEUDO_COST_SEARCH
119PORTFOLIO_WITH_QUICK_RESTART_SEARCH = (
120 sat_parameters_pb2.SatParameters.PORTFOLIO_WITH_QUICK_RESTART_SEARCH
122HINT_SEARCH = sat_parameters_pb2.SatParameters.HINT_SEARCH
123PARTIAL_FIXED_SEARCH = sat_parameters_pb2.SatParameters.PARTIAL_FIXED_SEARCH
124RANDOMIZED_SEARCH = sat_parameters_pb2.SatParameters.RANDOMIZED_SEARCH
127IntegralT = Union[int, np.int8, np.uint8, np.int32, np.uint32, np.int64, np.uint64]
160LiteralT = Union[cmh.Literal, IntegralT, bool]
161BoolVarT = cmh.Literal
162VariableT = Union[
"IntVar", IntegralT]
165LinearExprT = Union[LinearExpr,
"IntVar", IntegralT]
166ObjLinearExprT = Union[LinearExpr, NumberT]
168ArcT = Tuple[IntegralT, IntegralT, LiteralT]
169_IndexOrSeries = Union[pd.Index, pd.Series]
173 """Displays a flattened list of intervals."""
175 for i
in range(0, len(bounds), 2):
178 if bounds[i] == bounds[i + 1]:
179 out += str(bounds[i])
181 out += str(bounds[i]) +
".." + str(bounds[i + 1])
185def short_name(model: cp_model_pb2.CpModelProto, i: int) -> str:
186 """Returns a short name of an integer variable, or its negation."""
188 return f
"not({short_name(model, -i - 1)})"
189 v = model.variables[i]
192 elif len(v.domain) == 2
and v.domain[0] == v.domain[1]:
193 return str(v.domain[0])
195 return f
"[{display_bounds(v.domain)}]"
199 model: cp_model_pb2.CpModelProto, e: cp_model_pb2.LinearExpressionProto
201 """Pretty-print LinearExpressionProto instances."""
211 result = f
"-{var_name}"
213 result = f
"{coeff} * {var_name}"
215 result = f
"{result} + {e.offset}"
217 result = f
"{result} - {-e.offset}"
224 """An integer variable.
226 An IntVar is an object that can take on any integer value within defined
227 ranges. Variables appear in constraint like:
230 AllDifferent([x, y, z])
232 Solving a model is equivalent to finding, for each variable, a single value
233 from the set of initial values (called the initial domain), such that the
234 model is feasible, or optimal if you provided an objective function.
239 model: cp_model_pb2.CpModelProto,
240 domain: Union[int, sorted_interval_list.Domain],
244 """See CpModel.new_int_var below."""
245 self.
__var: cp_model_pb2.IntegerVariableProto
253 if isinstance(domain, IntegralTypes)
and name
is None:
254 cmh.BaseIntVar.__init__(self, int(domain), is_boolean)
255 self.
__var = model.variables[domain]
257 cmh.BaseIntVar.__init__(self, len(model.variables), is_boolean)
258 self.
__var = model.variables.add()
259 self.
__var.domain.extend(
260 cast(sorted_interval_list.Domain, domain).flattened_intervals()
263 self.
__var.name = name
266 def proto(self) -> cp_model_pb2.IntegerVariableProto:
267 """Returns the variable protobuf."""
271 """Returns true if self == other in the python sense."""
272 if not isinstance(other, IntVar):
277 if not self.
__var.name:
279 len(self.
__var.domain) == 2
280 and self.
__var.domain[0] == self.
__var.domain[1]
283 return str(self.
__var.domain[0])
285 return f
"BooleanVar({self.__index})"
287 return f
"IntVar({self.__index})"
289 return self.
__var.name
292 return f
"{self}({display_bounds(self.__var.domain)})"
298 return self.
__var.name
305 def Proto(self) -> cp_model_pb2.IntegerVariableProto:
312 """Base class for constraints.
314 Constraints are built by the CpModel through the add<XXX> methods.
315 Once created by the CpModel class, they are automatically added to the model.
316 The purpose of this class is to allow specification of enforcement literals
319 b = model.new_bool_var('b')
320 x = model.new_int_var(0, 10, 'x')
321 y = model.new_int_var(0, 10, 'y')
323 model.add(x + 2 * y == 5).only_enforce_if(b.negated())
330 self.
__index: int = len(cp_model.proto.constraints)
333 cp_model.proto.constraints.add()
343 """Adds an enforcement literal to the constraint.
345 This method adds one or more literals (that is, a boolean variable or its
346 negation) as enforcement literals. The conjunction of all these literals
347 determines whether the constraint is active or not. It acts as an
348 implication, so if the conjunction is true, it implies that the constraint
349 must be enforced. If it is false, then the constraint is ignored.
351 BoolOr, BoolAnd, and linear constraints all support enforcement literals.
354 *boolvar: One or more Boolean literals.
360 if (cmn.is_boolean(lit)
and lit)
or (
361 isinstance(lit, IntegralTypes)
and lit == 1
365 elif (cmn.is_boolean(lit)
and not lit)
or (
366 isinstance(lit, IntegralTypes)
and lit == 0
373 cast(cmh.Literal, lit).index
378 """Sets the name of the constraint."""
387 """Returns the name of the constraint."""
394 """Returns the index of the constraint in the model."""
398 def proto(self) -> cp_model_pb2.ConstraintProto:
399 """Returns the constraint protobuf."""
404 OnlyEnforceIf = only_enforce_if
413 def Proto(self) -> cp_model_pb2.ConstraintProto:
420 """Stores all integer variables of the model."""
429 def get(self, index: int) -> IntVar:
430 if index < 0
or index >= len(self.
__var_list):
431 raise ValueError(
"Index out of bounds.")
436 proto: cp_model_pb2.LinearExpressionProto,
438 """Recreate a LinearExpr from a LinearExpressionProto."""
439 num_elements = len(proto.vars)
440 if num_elements == 0:
442 elif num_elements == 1:
443 var = self.
get(proto.vars[0])
444 return LinearExpr.affine(
445 var, proto.coeffs[0], proto.offset
449 for var_index
in range(len(proto.vars)):
450 var = self.
get(var_index)
451 variables.append(var)
452 if proto.offset != 0:
454 coeffs.extend(proto.coeffs)
456 variables.append(proto.offset)
457 return LinearExpr.weighted_sum(variables, coeffs)
459 return LinearExpr.weighted_sum(variables, proto.coeffs)
463 """Represents an Interval variable.
465 An interval variable is both a constraint and a variable. It is defined by
466 three integer variables: start, size, and end.
468 It is a constraint because, internally, it enforces that start + size == end.
470 It is also a variable as it can appear in specific scheduling constraints:
471 NoOverlap, NoOverlap2D, Cumulative.
473 Optionally, an enforcement literal can be added to this constraint, in which
474 case these scheduling constraints will ignore interval variables with
475 enforcement literals assigned to false. Conversely, these constraints will
476 also set these enforcement literals to false if they cannot fit these
477 intervals into the schedule.
480 ValueError: if start, size, end are not defined, or have the wrong type.
485 model: cp_model_pb2.CpModelProto,
486 var_list: VariableList,
487 start: Union[cp_model_pb2.LinearExpressionProto, int],
488 size: Optional[cp_model_pb2.LinearExpressionProto],
489 end: Optional[cp_model_pb2.LinearExpressionProto],
490 is_present_index: Optional[int],
493 self.
__model: cp_model_pb2.CpModelProto = model
496 self.
__ct: cp_model_pb2.ConstraintProto
504 if isinstance(start, int):
506 raise ValueError(
"size should be None")
508 raise ValueError(
"end should be None")
509 if is_present_index
is not None:
510 raise ValueError(
"is_present_index should be None")
511 self.
__index = cast(int, start)
514 self.
__index = len(model.constraints)
517 raise TypeError(
"start is not defined")
518 self.
__ct.interval.start.CopyFrom(start)
520 raise TypeError(
"size is not defined")
521 self.
__ct.interval.size.CopyFrom(size)
523 raise TypeError(
"end is not defined")
524 self.
__ct.interval.end.CopyFrom(end)
525 if is_present_index
is not None:
526 self.
__ct.enforcement_literal.append(is_present_index)
528 self.
__ct.name = name
532 """Returns the index of the interval constraint in the model."""
536 def proto(self) -> cp_model_pb2.IntervalConstraintProto:
537 """Returns the interval protobuf."""
538 return self.
__ct.interval
541 return self.
__ct.name
544 interval = self.
__ct.interval
545 if self.
__ct.enforcement_literal:
547 f
"{self.__ct.name}(start ="
548 f
" {short_expr_name(self.__model, interval.start)}, size ="
549 f
" {short_expr_name(self.__model, interval.size)}, end ="
550 f
" {short_expr_name(self.__model, interval.end)}, is_present ="
551 f
" {short_name(self.__model, self.__ct.enforcement_literal[0])})"
555 f
"{self.__ct.name}(start ="
556 f
" {short_expr_name(self.__model, interval.start)}, size ="
557 f
" {short_expr_name(self.__model, interval.size)}, end ="
558 f
" {short_expr_name(self.__model, interval.end)})"
563 if not self.
__ct or not self.
__ct.name:
565 return self.
__ct.name
584 def Proto(self) -> cp_model_pb2.IntervalConstraintProto:
587 StartExpr = start_expr
595 """Checks if literal is either True, or a Boolean literals fixed to True."""
596 if isinstance(literal, IntVar):
597 proto = literal.proto
598 return len(proto.domain) == 2
and proto.domain[0] == 1
and proto.domain[1] == 1
599 if isinstance(literal, cmh.NotBooleanVariable):
600 proto = literal.negated().proto
601 return len(proto.domain) == 2
and proto.domain[0] == 0
and proto.domain[1] == 0
602 if isinstance(literal, IntegralTypes):
603 return int(literal) == 1
608 """Checks if literal is either False, or a Boolean literals fixed to False."""
609 if isinstance(literal, IntVar):
610 proto = literal.proto
611 return len(proto.domain) == 2
and proto.domain[0] == 0
and proto.domain[1] == 0
612 if isinstance(literal, cmh.NotBooleanVariable):
613 proto = literal.negated().proto
614 return len(proto.domain) == 2
and proto.domain[0] == 1
and proto.domain[1] == 1
615 if isinstance(literal, IntegralTypes):
616 return int(literal) == 0
621 """Methods for building a CP model.
623 Methods beginning with:
625 * ```New``` create integer, boolean, or interval variables.
626 * ```add``` create new constraints and add them to the model.
630 self.
__model: cp_model_pb2.CpModelProto = cp_model_pb2.CpModelProto()
637 """Returns the name of the model."""
644 """Sets the name of the model."""
650 """Appends an integer variable to the list of variables."""
659 proto: cp_model_pb2.LinearExpressionProto,
663 def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar:
664 """Create an integer variable with domain [lb, ub].
666 The CP-SAT solver is limited to integer variables. If you have fractional
667 values, scale them up so that they become integers; if you have strings,
668 encode them as integers.
671 lb: Lower bound for the variable.
672 ub: Upper bound for the variable.
673 name: The name of the variable.
676 a variable whose domain is [lb, ub].
678 domain_is_boolean = lb >= 0
and ub <= 1
682 sorted_interval_list.Domain(lb, ub),
689 self, domain: sorted_interval_list.Domain, name: str
691 """Create an integer variable from a domain.
693 A domain is a set of integers specified by a collection of intervals.
694 For example, `model.new_int_var_from_domain(cp_model.
695 Domain.from_intervals([[1, 2], [4, 6]]), 'x')`
698 domain: An instance of the Domain class.
699 name: The name of the variable.
702 a variable whose domain is the given domain.
704 domain_is_boolean = domain.min() >= 0
and domain.max() <= 1
710 """Creates a 0-1 variable with the given name."""
712 IntVar(self.
__model, sorted_interval_list.Domain(0, 1),
True, name)
716 """Declares a constant integer."""
724 lower_bounds: Union[IntegralT, pd.Series],
725 upper_bounds: Union[IntegralT, pd.Series],
727 """Creates a series of (scalar-valued) variables with the given name.
730 name (str): Required. The name of the variable set.
731 index (pd.Index): Required. The index to use for the variable set.
732 lower_bounds (Union[int, pd.Series]): A lower bound for variables in the
733 set. If a `pd.Series` is passed in, it will be based on the
734 corresponding values of the pd.Series.
735 upper_bounds (Union[int, pd.Series]): An upper bound for variables in the
736 set. If a `pd.Series` is passed in, it will be based on the
737 corresponding values of the pd.Series.
740 pd.Series: The variable set indexed by its corresponding dimensions.
743 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
744 ValueError: if the `name` is not a valid identifier or already exists.
745 ValueError: if the `lowerbound` is greater than the `upperbound`.
746 ValueError: if the index of `lower_bound`, or `upper_bound` does not match
749 if not isinstance(index, pd.Index):
750 raise TypeError(
"Non-index object is used as index")
751 if not name.isidentifier():
752 raise ValueError(f
"name={name!r} is not a valid identifier")
754 isinstance(lower_bounds, IntegralTypes)
755 and isinstance(upper_bounds, IntegralTypes)
756 and lower_bounds > upper_bounds
759 f
"lower_bound={lower_bounds} is greater than"
760 f
" upper_bound={upper_bounds} for variable set={name}"
777 domain=sorted_interval_list.Domain(
778 lower_bounds[i], upper_bounds[i]
780 is_boolean=lower_bounds[i] >= 0
and upper_bounds[i] <= 1,
792 """Creates a series of (scalar-valued) variables with the given name.
795 name (str): Required. The name of the variable set.
796 index (pd.Index): Required. The index to use for the variable set.
799 pd.Series: The variable set indexed by its corresponding dimensions.
802 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
803 ValueError: if the `name` is not a valid identifier or already exists.
805 if not isinstance(index, pd.Index):
806 raise TypeError(
"Non-index object is used as index")
807 if not name.isidentifier():
808 raise ValueError(f
"name={name!r} is not a valid identifier")
817 domain=sorted_interval_list.Domain(0, 1),
828 self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT
830 """Adds the constraint: `lb <= linear_expr <= ub`."""
832 linear_expr, sorted_interval_list.Domain(lb, ub)
837 linear_expr: LinearExprT,
838 domain: sorted_interval_list.Domain,
840 """Adds the constraint: `linear_expr` in `domain`."""
841 if isinstance(linear_expr, LinearExpr):
845 "Cannot add a linear expression containing floating point"
846 f
" coefficients or constants: {type(linear_expr).__name__!r}"
849 if isinstance(linear_expr, IntegralTypes):
850 if not domain.contains(int(linear_expr)):
856 f
" CpModel.add_linear_expression_in_domain({type(linear_expr).__name__!r})"
859 def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint:
860 """Adds a `BoundedLinearExpression` to the model.
863 ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
866 An instance of the `Constraint` class.
869 TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean.
871 if isinstance(ct, BoundedLinearExpression):
873 model_ct = self.
__model.constraints[result.index]
875 model_ct.linear.vars.append(var.index)
876 model_ct.linear.coeffs.extend(ct.coeffs)
877 model_ct.linear.domain.extend(
879 cmn.capped_subtraction(x, ct.offset)
880 for x
in ct.bounds.flattened_intervals()
884 if ct
and cmn.is_boolean(ct):
886 if not ct
and cmn.is_boolean(ct):
888 raise TypeError(f
"not supported: CpModel.add({type(ct).__name__!r})")
899 """Adds AllDifferent(expressions).
901 This constraint forces all expressions to have different values.
904 *expressions: simple expressions of the form a * var + constant.
907 An instance of the `Constraint` class.
910 model_ct = self.
__model.constraints[ct.index]
912 model_ct.all_diff.exprs.extend(
920 expressions: Sequence[LinearExprT],
923 """Adds the element constraint: `expressions[index] == target`.
926 index: The index of the selected expression in the array. It must be an
927 affine expression (a * var + b).
928 expressions: A list of affine expressions.
929 target: The expression constrained to be equal to the selected expression.
930 It must be an affine expression (a * var + b).
933 An instance of the `Constraint` class.
937 raise ValueError(
"add_element expects a non-empty expressions array")
939 if isinstance(index, IntegralTypes):
940 expression: LinearExprT = list(expressions)[int(index)]
941 return self.
add(expression == target)
944 model_ct = self.
__model.constraints[ct.index]
946 model_ct.element.exprs.extend(
953 """Adds Circuit(arcs).
955 Adds a circuit constraint from a sparse list of arcs that encode the graph.
957 A circuit is a unique Hamiltonian cycle in a subgraph of the total
958 graph. In case a node 'i' is not in the cycle, then there must be a
959 loop arc 'i -> i' associated with a true literal. Otherwise
960 this constraint will fail.
963 arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
964 literal). The arc is selected in the circuit if the literal is true.
965 Both source_node and destination_node must be integers between 0 and the
969 An instance of the `Constraint` class.
972 ValueError: If the list of arcs is empty.
975 raise ValueError(
"add_circuit expects a non-empty array of arcs")
977 model_ct = self.
__model.constraints[ct.index]
979 model_ct.circuit.tails.append(arc[0])
980 model_ct.circuit.heads.append(arc[1])
985 """Adds a multiple circuit constraint, aka the 'VRP' constraint.
987 The direct graph where arc #i (from tails[i] to head[i]) is present iff
988 literals[i] is true must satisfy this set of properties:
989 - #incoming arcs == 1 except for node 0.
990 - #outgoing arcs == 1 except for node 0.
991 - for node zero, #incoming arcs == #outgoing arcs.
992 - There are no duplicate arcs.
993 - Self-arcs are allowed except for node 0.
994 - There is no cycle in this graph, except through node 0.
997 arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
998 literal). The arc is selected in the circuit if the literal is true.
999 Both source_node and destination_node must be integers between 0 and the
1000 number of nodes - 1.
1003 An instance of the `Constraint` class.
1006 ValueError: If the list of arcs is empty.
1009 raise ValueError(
"add_multiple_circuit expects a non-empty array of arcs")
1011 model_ct = self.
__model.constraints[ct.index]
1013 model_ct.routes.tails.append(arc[0])
1014 model_ct.routes.heads.append(arc[1])
1020 expressions: Sequence[LinearExprT],
1021 tuples_list: Iterable[Sequence[IntegralT]],
1023 """Adds AllowedAssignments(expressions, tuples_list).
1025 An AllowedAssignments constraint is a constraint on an array of affine
1026 expressions, which requires that when all expressions are assigned values,
1028 resulting array equals one of the tuples in `tuple_list`.
1031 expressions: A list of affine expressions (a * var + b).
1032 tuples_list: A list of admissible tuples. Each tuple must have the same
1033 length as the expressions, and the ith value of a tuple corresponds to
1037 An instance of the `Constraint` class.
1040 TypeError: If a tuple does not have the same size as the list of
1042 ValueError: If the array of expressions is empty.
1047 "add_allowed_assignments expects a non-empty expressions array"
1051 model_ct = self.
__model.constraints[ct.index]
1052 model_ct.table.exprs.extend(
1055 arity: int = len(expressions)
1056 for one_tuple
in tuples_list:
1057 if len(one_tuple) != arity:
1058 raise TypeError(f
"Tuple {one_tuple!r} has the wrong arity")
1062 for one_tuple
in tuples_list:
1063 model_ct.table.values.extend(one_tuple)
1064 except ValueError
as ex:
1066 "add_xxx_assignment: Not an integer or does not fit in an int64_t:"
1067 f
" {type(ex.args).__name__!r}"
1074 expressions: Sequence[LinearExprT],
1075 tuples_list: Iterable[Sequence[IntegralT]],
1077 """Adds add_forbidden_assignments(expressions, [tuples_list]).
1079 A ForbiddenAssignments constraint is a constraint on an array of affine
1080 expressions where the list of impossible combinations is provided in the
1084 expressions: A list of affine expressions (a * var + b).
1085 tuples_list: A list of forbidden tuples. Each tuple must have the same
1086 length as the expressions, and the *i*th value of a tuple corresponds to
1087 the *i*th expression.
1090 An instance of the `Constraint` class.
1093 TypeError: If a tuple does not have the same size as the list of
1095 ValueError: If the array of expressions is empty.
1100 "add_forbidden_assignments expects a non-empty expressions array"
1103 index: int = len(self.
__model.constraints)
1105 self.
__model.constraints[index].table.negated =
True
1110 transition_expressions: Sequence[LinearExprT],
1111 starting_state: IntegralT,
1112 final_states: Sequence[IntegralT],
1113 transition_triples: Sequence[Tuple[IntegralT, IntegralT, IntegralT]],
1115 """Adds an automaton constraint.
1117 An automaton constraint takes a list of affine expressions (a * var + b) (of
1118 size *n*), an initial state, a set of final states, and a set of
1119 transitions. A transition is a triplet (*tail*, *transition*, *head*), where
1120 *tail* and *head* are states, and *transition* is the label of an arc from
1121 *head* to *tail*, corresponding to the value of one expression in the list
1125 This automaton will be unrolled into a flow with *n* + 1 phases. Each phase
1126 contains the possible states of the automaton. The first state contains the
1127 initial state. The last phase contains the final states.
1129 Between two consecutive phases *i* and *i* + 1, the automaton creates a set
1130 of arcs. For each transition (*tail*, *transition*, *head*), it will add
1131 an arc from the state *tail* of phase *i* and the state *head* of phase
1132 *i* + 1. This arc is labeled by the value *transition* of the expression
1133 `expressions[i]`. That is, this arc can only be selected if `expressions[i]`
1134 is assigned the value *transition*.
1136 A feasible solution of this constraint is an assignment of expressions such
1137 that, starting from the initial state in phase 0, there is a path labeled by
1138 the values of the expressions that ends in one of the final states in the
1142 transition_expressions: A non-empty list of affine expressions (a * var +
1143 b) whose values correspond to the labels of the arcs traversed by the
1145 starting_state: The initial state of the automaton.
1146 final_states: A non-empty list of admissible final states.
1147 transition_triples: A list of transitions for the automaton, in the
1148 following format (current_state, variable_value, next_state).
1151 An instance of the `Constraint` class.
1154 ValueError: if `transition_expressions`, `final_states`, or
1155 `transition_triples` are empty.
1158 if not transition_expressions:
1160 "add_automaton expects a non-empty transition_expressions array"
1162 if not final_states:
1163 raise ValueError(
"add_automaton expects some final states")
1165 if not transition_triples:
1166 raise ValueError(
"add_automaton expects some transition triples")
1169 model_ct = self.
__model.constraints[ct.index]
1170 model_ct.automaton.exprs.extend(
1173 model_ct.automaton.starting_state = starting_state
1174 for v
in final_states:
1175 model_ct.automaton.final_states.append(v)
1176 for t
in transition_triples:
1178 raise TypeError(f
"Tuple {t!r} has the wrong arity (!= 3)")
1179 model_ct.automaton.transition_tail.append(t[0])
1180 model_ct.automaton.transition_label.append(t[1])
1181 model_ct.automaton.transition_head.append(t[2])
1186 variables: Sequence[VariableT],
1187 inverse_variables: Sequence[VariableT],
1189 """Adds Inverse(variables, inverse_variables).
1191 An inverse constraint enforces that if `variables[i]` is assigned a value
1192 `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa.
1195 variables: An array of integer variables.
1196 inverse_variables: An array of integer variables.
1199 An instance of the `Constraint` class.
1202 TypeError: if variables and inverse_variables have different lengths, or
1206 if not variables
or not inverse_variables:
1207 raise TypeError(
"The Inverse constraint does not accept empty arrays")
1208 if len(variables) != len(inverse_variables):
1210 "In the inverse constraint, the two array variables and"
1211 " inverse_variables must have the same length."
1214 model_ct = self.
__model.constraints[ct.index]
1215 model_ct.inverse.f_direct.extend([self.
get_or_make_index(x)
for x
in variables])
1216 model_ct.inverse.f_inverse.extend(
1223 times: Iterable[LinearExprT],
1224 level_changes: Iterable[LinearExprT],
1228 """Adds Reservoir(times, level_changes, min_level, max_level).
1230 Maintains a reservoir level within bounds. The water level starts at 0, and
1231 at any time, it must be between min_level and max_level.
1233 If the affine expression `times[i]` is assigned a value t, then the current
1234 level changes by `level_changes[i]`, which is constant, at time t.
1236 Note that min level must be <= 0, and the max level must be >= 0. Please
1237 use fixed level_changes to simulate initial state.
1239 Therefore, at any time:
1240 sum(level_changes[i] if times[i] <= t) in [min_level, max_level]
1243 times: A list of 1-var affine expressions (a * x + b) which specify the
1244 time of the filling or emptying the reservoir.
1245 level_changes: A list of integer values that specifies the amount of the
1246 emptying or filling. Currently, variable demands are not supported.
1247 min_level: At any time, the level of the reservoir must be greater or
1248 equal than the min level.
1249 max_level: At any time, the level of the reservoir must be less or equal
1253 An instance of the `Constraint` class.
1256 ValueError: if max_level < min_level.
1258 ValueError: if max_level < 0.
1260 ValueError: if min_level > 0
1263 if max_level < min_level:
1264 raise ValueError(
"Reservoir constraint must have a max_level >= min_level")
1267 raise ValueError(
"Reservoir constraint must have a max_level >= 0")
1270 raise ValueError(
"Reservoir constraint must have a min_level <= 0")
1273 model_ct = self.
__model.constraints[ct.index]
1274 model_ct.reservoir.time_exprs.extend(
1277 model_ct.reservoir.level_changes.extend(
1280 model_ct.reservoir.min_level = min_level
1281 model_ct.reservoir.max_level = max_level
1286 times: Iterable[LinearExprT],
1287 level_changes: Iterable[LinearExprT],
1288 actives: Iterable[LiteralT],
1292 """Adds Reservoir(times, level_changes, actives, min_level, max_level).
1294 Maintains a reservoir level within bounds. The water level starts at 0, and
1295 at any time, it must be between min_level and max_level.
1297 If the variable `times[i]` is assigned a value t, and `actives[i]` is
1298 `True`, then the current level changes by `level_changes[i]`, which is
1302 Note that min level must be <= 0, and the max level must be >= 0. Please
1303 use fixed level_changes to simulate initial state.
1305 Therefore, at any time:
1306 sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level,
1310 The array of boolean variables 'actives', if defined, indicates which
1311 actions are actually performed.
1314 times: A list of 1-var affine expressions (a * x + b) which specify the
1315 time of the filling or emptying the reservoir.
1316 level_changes: A list of integer values that specifies the amount of the
1317 emptying or filling. Currently, variable demands are not supported.
1318 actives: a list of boolean variables. They indicates if the
1319 emptying/refilling events actually take place.
1320 min_level: At any time, the level of the reservoir must be greater or
1321 equal than the min level.
1322 max_level: At any time, the level of the reservoir must be less or equal
1326 An instance of the `Constraint` class.
1329 ValueError: if max_level < min_level.
1331 ValueError: if max_level < 0.
1333 ValueError: if min_level > 0
1336 if max_level < min_level:
1337 raise ValueError(
"Reservoir constraint must have a max_level >= min_level")
1340 raise ValueError(
"Reservoir constraint must have a max_level >= 0")
1343 raise ValueError(
"Reservoir constraint must have a min_level <= 0")
1346 model_ct = self.
__model.constraints[ct.index]
1347 model_ct.reservoir.time_exprs.extend(
1350 model_ct.reservoir.level_changes.extend(
1353 model_ct.reservoir.active_literals.extend(
1356 model_ct.reservoir.min_level = min_level
1357 model_ct.reservoir.max_level = max_level
1361 self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0
1363 """Adds `var == i + offset <=> bool_var_array[i] == true for all i`."""
1365 for i, bool_var
in enumerate(bool_var_array):
1366 b_index = bool_var.index
1367 var_index = var.index
1368 model_ct = self.
__model.constraints.add()
1369 model_ct.linear.vars.append(var_index)
1370 model_ct.linear.coeffs.append(1)
1371 offset_as_int = int(offset)
1372 model_ct.linear.domain.extend([offset_as_int + i, offset_as_int + i])
1373 model_ct.enforcement_literal.append(b_index)
1375 model_ct = self.
__model.constraints.add()
1376 model_ct.linear.vars.append(var_index)
1377 model_ct.linear.coeffs.append(1)
1378 model_ct.enforcement_literal.append(-b_index - 1)
1379 if offset + i - 1 >= INT_MIN:
1380 model_ct.linear.domain.extend([INT_MIN, offset_as_int + i - 1])
1381 if offset + i + 1 <= INT_MAX:
1382 model_ct.linear.domain.extend([offset_as_int + i + 1, INT_MAX])
1385 """Adds `a => b` (`a` implies `b`)."""
1387 model_ct = self.
__model.constraints[ct.index]
1393 def add_bool_or(self, literals: Iterable[LiteralT]) -> Constraint: ...
1399 """Adds `Or(literals) == true`: sum(literals) >= 1."""
1401 model_ct = self.
__model.constraints[ct.index]
1402 model_ct.bool_or.literals.extend(
1417 """Same as `add_bool_or`: `sum(literals) >= 1`."""
1427 """Adds `AtMostOne(literals)`: `sum(literals) <= 1`."""
1429 model_ct = self.
__model.constraints[ct.index]
1430 model_ct.at_most_one.literals.extend(
1445 """Adds `ExactlyOne(literals)`: `sum(literals) == 1`."""
1447 model_ct = self.
__model.constraints[ct.index]
1448 model_ct.exactly_one.literals.extend(
1463 """Adds `And(literals) == true`."""
1465 model_ct = self.
__model.constraints[ct.index]
1466 model_ct.bool_and.literals.extend(
1481 """Adds `XOr(literals) == true`.
1483 In contrast to add_bool_or and add_bool_and, it does not support
1487 *literals: the list of literals in the constraint.
1490 An `Constraint` object.
1493 model_ct = self.
__model.constraints[ct.index]
1494 model_ct.bool_xor.literals.extend(
1503 self, target: LinearExprT, exprs: Iterable[LinearExprT]
1505 """Adds `target == Min(exprs)`."""
1507 model_ct = self.
__model.constraints[ct.index]
1508 model_ct.lin_max.exprs.extend(
1515 self, target: LinearExprT, exprs: Iterable[LinearExprT]
1517 """Adds `target == Max(exprs)`."""
1519 model_ct = self.
__model.constraints[ct.index]
1525 self, target: LinearExprT, num: LinearExprT, denom: LinearExprT
1527 """Adds `target == num // denom` (integer division rounded towards 0)."""
1529 model_ct = self.
__model.constraints[ct.index]
1536 """Adds `target == Abs(expr)`."""
1538 model_ct = self.
__model.constraints[ct.index]
1545 self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT
1547 """Adds `target = expr % mod`.
1549 It uses the C convention, that is the result is the remainder of the
1550 integral division rounded towards 0.
1559 target: the target expression.
1560 expr: the expression to compute the modulo of.
1561 mod: the modulus expression.
1564 A `Constraint` object.
1567 model_ct = self.
__model.constraints[ct.index]
1575 target: LinearExprT,
1576 *expressions: Union[Iterable[LinearExprT], LinearExprT],
1578 """Adds `target == expressions[0] * .. * expressions[n]`."""
1580 model_ct = self.
__model.constraints[ct.index]
1581 model_ct.int_prod.exprs.extend(
1593 self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str
1595 """Creates an interval variable from start, size, and end.
1597 An interval variable is a constraint, that is itself used in other
1598 constraints like NoOverlap.
1600 Internally, it ensures that `start + size == end`.
1603 start: The start of the interval. It must be of the form a * var + b.
1604 size: The size of the interval. It must be of the form a * var + b.
1605 end: The end of the interval. It must be of the form a * var + b.
1606 name: The name of the interval variable.
1609 An `IntervalVar` object.
1615 if len(start_expr.vars) > 1:
1617 "cp_model.new_interval_var: start must be 1-var affine or constant."
1619 if len(size_expr.vars) > 1:
1621 "cp_model.new_interval_var: size must be 1-var affine or constant."
1623 if len(end_expr.vars) > 1:
1625 "cp_model.new_interval_var: end must be 1-var affine or constant."
1641 starts: Union[LinearExprT, pd.Series],
1642 sizes: Union[LinearExprT, pd.Series],
1643 ends: Union[LinearExprT, pd.Series],
1645 """Creates a series of interval variables with the given name.
1648 name (str): Required. The name of the variable set.
1649 index (pd.Index): Required. The index to use for the variable set.
1650 starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1651 set. If a `pd.Series` is passed in, it will be based on the
1652 corresponding values of the pd.Series.
1653 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1654 set. If a `pd.Series` is passed in, it will be based on the
1655 corresponding values of the pd.Series.
1656 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1657 set. If a `pd.Series` is passed in, it will be based on the
1658 corresponding values of the pd.Series.
1661 pd.Series: The interval variable set indexed by its corresponding
1665 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1666 ValueError: if the `name` is not a valid identifier or already exists.
1667 ValueError: if the all the indexes do not match.
1669 if not isinstance(index, pd.Index):
1670 raise TypeError(
"Non-index object is used as index")
1671 if not name.isidentifier():
1672 raise ValueError(f
"name={name!r} is not a valid identifier")
1679 interval_array.append(
1684 name=f
"{name}[{i}]",
1687 return pd.Series(index=index, data=interval_array)
1690 self, start: LinearExprT, size: IntegralT, name: str
1692 """Creates an interval variable from start, and a fixed size.
1694 An interval variable is a constraint, that is itself used in other
1695 constraints like NoOverlap.
1698 start: The start of the interval. It must be of the form a * var + b.
1699 size: The size of the interval. It must be an integer value.
1700 name: The name of the interval variable.
1703 An `IntervalVar` object.
1708 if len(start_expr.vars) > 1:
1710 "cp_model.new_interval_var: start must be affine or constant."
1726 starts: Union[LinearExprT, pd.Series],
1727 sizes: Union[IntegralT, pd.Series],
1729 """Creates a series of interval variables with the given name.
1732 name (str): Required. The name of the variable set.
1733 index (pd.Index): Required. The index to use for the variable set.
1734 starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1735 set. If a `pd.Series` is passed in, it will be based on the
1736 corresponding values of the pd.Series.
1737 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1738 the set. If a `pd.Series` is passed in, it will be based on the
1739 corresponding values of the pd.Series.
1742 pd.Series: The interval variable set indexed by its corresponding
1746 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1747 ValueError: if the `name` is not a valid identifier or already exists.
1748 ValueError: if the all the indexes do not match.
1750 if not isinstance(index, pd.Index):
1751 raise TypeError(
"Non-index object is used as index")
1752 if not name.isidentifier():
1753 raise ValueError(f
"name={name!r} is not a valid identifier")
1759 interval_array.append(
1763 name=f
"{name}[{i}]",
1766 return pd.Series(index=index, data=interval_array)
1773 is_present: LiteralT,
1776 """Creates an optional interval var from start, size, end, and is_present.
1778 An optional interval variable is a constraint, that is itself used in other
1779 constraints like NoOverlap. This constraint is protected by a presence
1780 literal that indicates if it is active or not.
1782 Internally, it ensures that `is_present` implies `start + size ==
1786 start: The start of the interval. It must be of the form a * var + b.
1787 size: The size of the interval. It must be of the form a * var + b.
1788 end: The end of the interval. It must be of the form a * var + b.
1789 is_present: A literal that indicates if the interval is active or not. A
1790 inactive interval is simply ignored by all constraints.
1791 name: The name of the interval variable.
1794 An `IntervalVar` object.
1802 if len(start_expr.vars) > 1:
1804 "cp_model.new_interval_var: start must be affine or constant."
1806 if len(size_expr.vars) > 1:
1808 "cp_model.new_interval_var: size must be affine or constant."
1810 if len(end_expr.vars) > 1:
1812 "cp_model.new_interval_var: end must be affine or constant."
1828 starts: Union[LinearExprT, pd.Series],
1829 sizes: Union[LinearExprT, pd.Series],
1830 ends: Union[LinearExprT, pd.Series],
1831 are_present: Union[LiteralT, pd.Series],
1833 """Creates a series of interval variables with the given name.
1836 name (str): Required. The name of the variable set.
1837 index (pd.Index): Required. The index to use for the variable set.
1838 starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1839 set. If a `pd.Series` is passed in, it will be based on the
1840 corresponding values of the pd.Series.
1841 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1842 set. If a `pd.Series` is passed in, it will be based on the
1843 corresponding values of the pd.Series.
1844 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1845 set. If a `pd.Series` is passed in, it will be based on the
1846 corresponding values of the pd.Series.
1847 are_present (Union[LiteralT, pd.Series]): The performed literal of each
1848 interval in the set. If a `pd.Series` is passed in, it will be based on
1849 the corresponding values of the pd.Series.
1852 pd.Series: The interval variable set indexed by its corresponding
1856 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1857 ValueError: if the `name` is not a valid identifier or already exists.
1858 ValueError: if the all the indexes do not match.
1860 if not isinstance(index, pd.Index):
1861 raise TypeError(
"Non-index object is used as index")
1862 if not name.isidentifier():
1863 raise ValueError(f
"name={name!r} is not a valid identifier")
1872 interval_array.append(
1877 is_present=are_present[i],
1878 name=f
"{name}[{i}]",
1881 return pd.Series(index=index, data=interval_array)
1887 is_present: LiteralT,
1890 """Creates an interval variable from start, and a fixed size.
1892 An interval variable is a constraint, that is itself used in other
1893 constraints like NoOverlap.
1896 start: The start of the interval. It must be of the form a * var + b.
1897 size: The size of the interval. It must be an integer value.
1898 is_present: A literal that indicates if the interval is active or not. A
1899 inactive interval is simply ignored by all constraints.
1900 name: The name of the interval variable.
1903 An `IntervalVar` object.
1908 if len(start_expr.vars) > 1:
1910 "cp_model.new_interval_var: start must be affine or constant."
1927 starts: Union[LinearExprT, pd.Series],
1928 sizes: Union[IntegralT, pd.Series],
1929 are_present: Union[LiteralT, pd.Series],
1931 """Creates a series of interval variables with the given name.
1934 name (str): Required. The name of the variable set.
1935 index (pd.Index): Required. The index to use for the variable set.
1936 starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1937 set. If a `pd.Series` is passed in, it will be based on the
1938 corresponding values of the pd.Series.
1939 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1940 the set. If a `pd.Series` is passed in, it will be based on the
1941 corresponding values of the pd.Series.
1942 are_present (Union[LiteralT, pd.Series]): The performed literal of each
1943 interval in the set. If a `pd.Series` is passed in, it will be based on
1944 the corresponding values of the pd.Series.
1947 pd.Series: The interval variable set indexed by its corresponding
1951 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1952 ValueError: if the `name` is not a valid identifier or already exists.
1953 ValueError: if the all the indexes do not match.
1955 if not isinstance(index, pd.Index):
1956 raise TypeError(
"Non-index object is used as index")
1957 if not name.isidentifier():
1958 raise ValueError(f
"name={name!r} is not a valid identifier")
1965 interval_array.append(
1969 is_present=are_present[i],
1970 name=f
"{name}[{i}]",
1973 return pd.Series(index=index, data=interval_array)
1976 """Adds NoOverlap(interval_vars).
1978 A NoOverlap constraint ensures that all present intervals do not overlap
1982 interval_vars: The list of interval variables to constrain.
1985 An instance of the `Constraint` class.
1988 model_ct = self.
__model.constraints[ct.index]
1989 model_ct.no_overlap.intervals.extend(
1996 x_intervals: Iterable[IntervalVar],
1997 y_intervals: Iterable[IntervalVar],
1999 """Adds NoOverlap2D(x_intervals, y_intervals).
2001 A NoOverlap2D constraint ensures that all present rectangles do not overlap
2002 on a plane. Each rectangle is aligned with the X and Y axis, and is defined
2003 by two intervals which represent its projection onto the X and Y axis.
2005 Furthermore, one box is optional if at least one of the x or y interval is
2009 x_intervals: The X coordinates of the rectangles.
2010 y_intervals: The Y coordinates of the rectangles.
2013 An instance of the `Constraint` class.
2016 model_ct = self.
__model.constraints[ct.index]
2017 model_ct.no_overlap_2d.x_intervals.extend(
2020 model_ct.no_overlap_2d.y_intervals.extend(
2027 intervals: Iterable[IntervalVar],
2028 demands: Iterable[LinearExprT],
2029 capacity: LinearExprT,
2031 """Adds Cumulative(intervals, demands, capacity).
2033 This constraint enforces that:
2037 if (start(intervals[i]) <= t < end(intervals[i])) and
2038 (intervals[i] is present)) <= capacity
2041 intervals: The list of intervals.
2042 demands: The list of demands for each interval. Each demand must be >= 0.
2043 Each demand can be a 1-var affine expression (a * x + b).
2044 capacity: The maximum capacity of the cumulative constraint. It can be a
2045 1-var affine expression (a * x + b).
2048 An instance of the `Constraint` class.
2051 model_ct = self.
__model.constraints[cumulative.index]
2052 model_ct.cumulative.intervals.extend(
2062 """Reset the model, and creates a new one from a CpModelProto instance."""
2065 clone.rebuild_var_and_constant_map()
2075 """Internal method used during model cloning."""
2076 for i, var
in enumerate(self.
__model.variables):
2077 if len(var.domain) == 2
and var.domain[0] == var.domain[1]:
2080 len(var.domain) == 2
and var.domain[0] >= 0
and var.domain[1] <= 1
2085 """Returns an already created Boolean variable from its index."""
2087 if not result.is_boolean:
2089 f
"get_bool_var_from_proto_index: index {index} does not reference a"
2095 """Returns an already created integer variable from its index."""
2099 """Returns an already created interval variable from its index."""
2100 if index < 0
or index >= len(self.
__model.constraints):
2102 f
"get_interval_var_from_proto_index: out of bound index {index}"
2104 ct = self.
__model.constraints[index]
2105 if not ct.HasField(
"interval"):
2107 f
"get_interval_var_from_proto_index: index {index} does not"
2108 " reference an" +
" interval variable"
2119 def proto(self) -> cp_model_pb2.CpModelProto:
2120 """Returns the underlying CpModelProto."""
2127 """Returns the index of a variable, its negation, or a number."""
2128 if isinstance(arg, IntVar):
2130 if isinstance(arg, IntegralTypes):
2133 f
"NotSupported: model.get_or_make_index({type(arg).__name__!r})"
2137 """Returns an index from a boolean expression."""
2138 if isinstance(arg, IntVar):
2141 if isinstance(arg, cmh.NotBooleanVariable):
2144 if isinstance(arg, IntegralTypes):
2149 arg = cmn.assert_is_zero_or_one(arg)
2151 if cmn.is_boolean(arg):
2154 "not supported:" f
" model.get_or_make_boolean_index({type(arg).__name__!r})"
2158 if not isinstance(arg, IntervalVar):
2160 f
"NotSupported: model.get_interval_index({type(arg).__name__!r})"
2169 return constant_var.index
2172 self, linear_expr: LinearExprT, negate: bool =
False
2173 ) -> cp_model_pb2.LinearExpressionProto:
2174 """Returns a LinearExpressionProto built from a LinearExpr instance."""
2175 result: cp_model_pb2.LinearExpressionProto = (
2176 cp_model_pb2.LinearExpressionProto()
2178 mult = -1
if negate
else 1
2179 if isinstance(linear_expr, IntegralTypes):
2180 result.offset = int(linear_expr) * mult
2184 flat_expr = cmh.FlatIntExpr(linear_expr)
2185 result.offset = flat_expr.offset
2186 for var
in flat_expr.vars:
2187 result.vars.append(var.index)
2188 for coeff
in flat_expr.coeffs:
2189 result.coeffs.append(coeff * mult)
2193 """Sets the objective of the model."""
2195 if isinstance(obj, IntegralTypes):
2196 self.
__model.objective.offset = int(obj)
2197 self.
__model.objective.scaling_factor = 1.0
2198 elif isinstance(obj, LinearExpr):
2199 if obj.is_integer():
2200 int_obj = cmh.FlatIntExpr(obj)
2201 for var
in int_obj.vars:
2202 self.
__model.objective.vars.append(var.index)
2204 self.
__model.objective.scaling_factor = 1.0
2205 self.
__model.objective.offset = int_obj.offset
2206 self.
__model.objective.coeffs.extend(int_obj.coeffs)
2208 self.
__model.objective.scaling_factor = -1.0
2209 self.
__model.objective.offset = -int_obj.offset
2210 for c
in int_obj.coeffs:
2211 self.
__model.objective.coeffs.append(-c)
2213 float_obj = cmh.FlatFloatExpr(obj)
2214 for var
in float_obj.vars:
2215 self.
__model.floating_point_objective.vars.append(var.index)
2216 self.
__model.floating_point_objective.coeffs.extend(float_obj.coeffs)
2217 self.
__model.floating_point_objective.maximize =
not minimize
2218 self.
__model.floating_point_objective.offset = float_obj.offset
2221 f
"TypeError: {type(obj).__name__!r} is not a valid objective"
2225 """Sets the objective of the model to minimize(obj)."""
2229 """Sets the objective of the model to maximize(obj)."""
2233 return self.
__model.HasField(
"objective")
or self.
__model.HasField(
2234 "floating_point_objective"
2238 self.
__model.ClearField(
"objective")
2239 self.
__model.ClearField(
"floating_point_objective")
2243 variables: Sequence[IntVar],
2244 var_strategy: cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy,
2245 domain_strategy: cp_model_pb2.DecisionStrategyProto.DomainReductionStrategy,
2247 """Adds a search strategy to the model.
2250 variables: a list of variables this strategy will assign.
2251 var_strategy: heuristic to choose the next variable to assign.
2252 domain_strategy: heuristic to reduce the domain of the selected variable.
2253 Currently, this is advanced code: the union of all strategies added to
2254 the model must be complete, i.e. instantiates all variables. Otherwise,
2258 strategy: cp_model_pb2.DecisionStrategyProto = (
2259 self.
__model.search_strategy.add()
2262 expr = strategy.exprs.add()
2264 expr.vars.append(v.index)
2265 expr.coeffs.append(1)
2267 expr.vars.append(self.
negated(v.index))
2268 expr.coeffs.append(-1)
2271 strategy.variable_selection_strategy = var_strategy
2272 strategy.domain_reduction_strategy = domain_strategy
2275 """Returns a string containing some model statistics."""
2276 return cmh.CpSatHelper.model_stats(self.
__model)
2279 """Returns a string indicating that the model is invalid."""
2280 return cmh.CpSatHelper.validate_model(self.
__model)
2283 """Write the model as a protocol buffer to 'file'.
2286 file: file to write the model to. If the filename ends with 'txt', the
2287 model will be written as a text file, otherwise, the binary format will
2291 True if the model was correctly written.
2293 return cmh.CpSatHelper.write_model_to_file(self.
__model, file)
2296 def add_hint(self, var: IntVar, value: int) ->
None: ...
2299 def add_hint(self, literal: BoolVarT, value: bool) ->
None: ...
2302 """Adds 'var == value' as a hint to the solver."""
2305 self.
__model.solution_hint.values.append(int(value))
2308 self.
__model.solution_hint.values.append(int(
not value))
2311 """Removes any solution hint from the model."""
2312 self.
__model.ClearField(
"solution_hint")
2315 """Adds the literal to the model as assumptions."""
2319 """Adds the literals to the model as assumptions."""
2320 for lit
in literals:
2324 """Removes all assumptions from the model."""
2325 self.
__model.ClearField(
"assumptions")
2329 if isinstance(x, IntVar):
2330 var = self.
__model.variables[x.index]
2331 if len(var.domain) != 2
or var.domain[0] < 0
or var.domain[1] > 1:
2333 f
"TypeError: {type(x).__name__!r} is not a boolean variable"
2335 elif not isinstance(x, cmh.NotBooleanVariable):
2337 f
"TypeError: {type(x).__name__!r} is not a boolean variable"
2349 def Proto(self) -> cp_model_pb2.CpModelProto:
2352 NewIntVar = new_int_var
2353 NewIntVarFromDomain = new_int_var_from_domain
2354 NewBoolVar = new_bool_var
2355 NewConstant = new_constant
2356 NewIntVarSeries = new_int_var_series
2357 NewBoolVarSeries = new_bool_var_series
2358 AddLinearConstraint = add_linear_constraint
2359 AddLinearExpressionInDomain = add_linear_expression_in_domain
2361 AddAllDifferent = add_all_different
2362 AddElement = add_element
2363 AddCircuit = add_circuit
2364 AddMultipleCircuit = add_multiple_circuit
2365 AddAllowedAssignments = add_allowed_assignments
2366 AddForbiddenAssignments = add_forbidden_assignments
2367 AddAutomaton = add_automaton
2368 AddInverse = add_inverse
2369 AddReservoirConstraint = add_reservoir_constraint
2370 AddReservoirConstraintWithActive = add_reservoir_constraint_with_active
2371 AddImplication = add_implication
2372 AddBoolOr = add_bool_or
2373 AddAtLeastOne = add_at_least_one
2374 AddAtMostOne = add_at_most_one
2375 AddExactlyOne = add_exactly_one
2376 AddBoolAnd = add_bool_and
2377 AddBoolXOr = add_bool_xor
2378 AddMinEquality = add_min_equality
2379 AddMaxEquality = add_max_equality
2380 AddDivisionEquality = add_division_equality
2381 AddAbsEquality = add_abs_equality
2382 AddModuloEquality = add_modulo_equality
2383 AddMultiplicationEquality = add_multiplication_equality
2384 NewIntervalVar = new_interval_var
2385 NewIntervalVarSeries = new_interval_var_series
2386 NewFixedSizeIntervalVar = new_fixed_size_interval_var
2387 NewOptionalIntervalVar = new_optional_interval_var
2388 NewOptionalIntervalVarSeries = new_optional_interval_var_series
2389 NewOptionalFixedSizeIntervalVar = new_optional_fixed_size_interval_var
2390 NewOptionalFixedSizeIntervalVarSeries = new_optional_fixed_size_interval_var_series
2391 AddNoOverlap = add_no_overlap
2392 AddNoOverlap2D = add_no_overlap_2d
2393 AddCumulative = add_cumulative
2395 GetBoolVarFromProtoIndex = get_bool_var_from_proto_index
2396 GetIntVarFromProtoIndex = get_int_var_from_proto_index
2397 GetIntervalVarFromProtoIndex = get_interval_var_from_proto_index
2400 HasObjective = has_objective
2401 ClearObjective = clear_objective
2402 AddDecisionStrategy = add_decision_strategy
2403 ModelStats = model_stats
2405 ExportToFile = export_to_file
2407 ClearHints = clear_hints
2408 AddAssumption = add_assumption
2409 AddAssumptions = add_assumptions
2410 ClearAssumptions = clear_assumptions
2417 args: Union[Tuple[LiteralT, ...], Iterable[LiteralT]],
2418) -> Union[Iterable[LiteralT], LiteralT]: ...
2423 args: Union[Tuple[LinearExprT, ...], Iterable[LinearExprT]],
2424) -> Union[Iterable[LinearExprT], LinearExprT]: ...
2428 if hasattr(args,
"__len__"):
2431 if isinstance(args[0], (NumberTypes, LinearExpr)):
2438 """Main solver class.
2440 The purpose of this class is to search for a solution to the model provided
2441 to the solve() method.
2443 Once solve() is called, this class allows inspecting the solution found
2444 with the value() and boolean_value() methods, as well as general statistics
2445 about the solve procedure.
2451 sat_parameters_pb2.SatParameters()
2456 self.
__lock: threading.Lock = threading.Lock()
2461 solution_callback: Optional[
"CpSolverSolutionCallback"] =
None,
2462 ) -> cp_model_pb2.CpSolverStatus:
2463 """Solves a problem and passes each solution to the callback if not null."""
2468 if solution_callback
is not None:
2481 if solution_callback
is not None:
2490 """Stops the current search asynchronously."""
2495 def value(self, expression: LinearExprT) -> int:
2496 """Returns the value of a linear expression after solve."""
2499 def values(self, variables: _IndexOrSeries) -> pd.Series:
2500 """Returns the values of the input variables.
2502 If `variables` is a `pd.Index`, then the output will be indexed by the
2503 variables. If `variables` is a `pd.Series` indexed by the underlying
2504 dimensions, then the output will be indexed by the same underlying
2508 variables (Union[pd.Index, pd.Series]): The set of variables from which to
2512 pd.Series: The values of all variables in the set.
2515 RuntimeError: if solve() has not been called.
2518 raise RuntimeError(
"solve() has not been called.")
2525 """Returns the boolean value of a literal after solve."""
2529 """Returns the values of the input variables.
2531 If `variables` is a `pd.Index`, then the output will be indexed by the
2532 variables. If `variables` is a `pd.Series` indexed by the underlying
2533 dimensions, then the output will be indexed by the same underlying
2537 variables (Union[pd.Index, pd.Series]): The set of variables from which to
2541 pd.Series: The values of all variables in the set.
2544 RuntimeError: if solve() has not been called.
2547 raise RuntimeError(
"solve() has not been called.")
2557 """Returns the value of the objective after solve."""
2562 """Returns the best lower (upper) bound found when min(max)imizing."""
2567 """Returns the number of boolean variables managed by the SAT solver."""
2572 """Returns the number of conflicts since the creation of the solver."""
2577 """Returns the number of search branches explored by the solver."""
2582 """Returns the wall time in seconds since the creation of the solver."""
2587 """Returns the user time in seconds since the creation of the solver."""
2592 """Returns the response object."""
2596 """Returns some statistics on the solution found as a string."""
2600 """Returns the indices of the infeasible assumptions."""
2604 """Returns the name of the status returned by solve()."""
2607 return cp_model_pb2.CpSolverStatus.Name(status)
2610 """Returns some information on the solve process.
2612 Returns some information on how the solution was found, or the reason
2613 why the model or the parameters are invalid.
2616 RuntimeError: if solve() has not been called.
2622 """Checks solve() has been called, and returns a response wrapper."""
2624 raise RuntimeError(
"solve() has not been called.")
2660 solution_callback: Optional[
"CpSolverSolutionCallback"] =
None,
2661 ) -> cp_model_pb2.CpSolverStatus:
2662 return self.
solve(model, solution_callback)
2679 def Value(self, expression: LinearExprT) -> int:
2680 return self.
value(expression)
2682 def Values(self, variables: _IndexOrSeries) -> pd.Series:
2683 return self.
values(variables)
2689 self, model: CpModel, callback:
"CpSolverSolutionCallback"
2690 ) -> cp_model_pb2.CpSolverStatus:
2691 """DEPRECATED Use solve() with the callback argument."""
2693 "solve_with_solution_callback is deprecated; use solve() with"
2694 +
"the callback argument.",
2697 return self.
solve(model, callback)
2700 self, model: CpModel, callback:
"CpSolverSolutionCallback"
2701 ) -> cp_model_pb2.CpSolverStatus:
2702 """DEPRECATED Use solve() with the right parameter.
2704 Search for all solutions of a satisfiability problem.
2706 This method searches for all feasible solutions of a given model.
2707 Then it feeds the solution to the callback.
2709 Note that the model cannot contain an objective.
2712 model: The model to solve.
2713 callback: The callback that will be called at each solution.
2716 The status of the solve:
2718 * *FEASIBLE* if some solutions have been found
2719 * *INFEASIBLE* if the solver has proved there are no solution
2720 * *OPTIMAL* if all solutions have been found
2723 "search_for_all_solutions is deprecated; use solve() with"
2724 +
"enumerate_all_solutions = True.",
2727 if model.has_objective():
2729 "Search for all solutions is only defined on satisfiability problems"
2732 enumerate_all = self.
parameters.enumerate_all_solutions
2733 self.
parameters.enumerate_all_solutions =
True
2735 status: cp_model_pb2.CpSolverStatus = self.
solve(model, callback)
2738 self.
parameters.enumerate_all_solutions = enumerate_all
2746 """Solution callback.
2748 This class implements a callback that will be called at each new solution
2749 found during search.
2751 The method on_solution_callback() will be called by the solver, and must be
2752 implemented. The current solution can be queried using the boolean_value()
2753 and value() methods.
2755 These methods returns the same information as their counterpart in the
2760 cmh.SolutionCallback.__init__(self)
2763 """Proxy for the same method in snake case."""
2764 self.on_solution_callback()
2767 """Returns the boolean value of a boolean literal.
2770 lit: A boolean variable or its negation.
2773 The Boolean value of the literal in the solution.
2776 RuntimeError: if `lit` is not a boolean variable or its negation.
2779 raise RuntimeError(
"solve() has not been called.")
2780 return self.BooleanValue(lit)
2782 def value(self, expression: LinearExprT) -> int:
2783 """Evaluates an linear expression in the current solution.
2786 expression: a linear expression of the model.
2789 An integer value equal to the evaluation of the linear expression
2790 against the current solution.
2793 RuntimeError: if 'expression' is not a LinearExpr.
2796 raise RuntimeError(
"solve() has not been called.")
2797 return self.Value(expression)
2800 return self.HasResponse()
2803 """Stops the current search asynchronously."""
2805 raise RuntimeError(
"solve() has not been called.")
2810 """Returns the value of the objective after solve."""
2812 raise RuntimeError(
"solve() has not been called.")
2813 return self.ObjectiveValue()
2817 """Returns the best lower (upper) bound found when min(max)imizing."""
2819 raise RuntimeError(
"solve() has not been called.")
2820 return self.BestObjectiveBound()
2824 """Returns the number of boolean variables managed by the SAT solver."""
2826 raise RuntimeError(
"solve() has not been called.")
2827 return self.NumBooleans()
2831 """Returns the number of conflicts since the creation of the solver."""
2833 raise RuntimeError(
"solve() has not been called.")
2834 return self.NumConflicts()
2838 """Returns the number of search branches explored by the solver."""
2840 raise RuntimeError(
"solve() has not been called.")
2841 return self.NumBranches()
2845 """Returns the number of integer propagations done by the solver."""
2847 raise RuntimeError(
"solve() has not been called.")
2848 return self.NumIntegerPropagations()
2852 """Returns the number of Boolean propagations done by the solver."""
2854 raise RuntimeError(
"solve() has not been called.")
2855 return self.NumBooleanPropagations()
2859 """Returns the determistic time in seconds since the creation of the solver."""
2861 raise RuntimeError(
"solve() has not been called.")
2862 return self.DeterministicTime()
2866 """Returns the wall time in seconds since the creation of the solver."""
2868 raise RuntimeError(
"solve() has not been called.")
2869 return self.WallTime()
2873 """Returns the user time in seconds since the creation of the solver."""
2875 raise RuntimeError(
"solve() has not been called.")
2876 return self.UserTime()
2880 """Returns the response object."""
2882 raise RuntimeError(
"solve() has not been called.")
2883 return self.Response()
2887 """Display the objective value and time of intermediate solutions."""
2890 CpSolverSolutionCallback.__init__(self)
2895 """Called on each new solution."""
2896 current_time = time.time()
2899 f
"Solution {self.__solution_count}, time ="
2900 f
" {current_time - self.__start_time:0.2f} s, objective = {obj}",
2906 """Returns the number of solutions found."""
2911 """Print intermediate solutions (objective, variable values, time)."""
2913 def __init__(self, variables: Sequence[IntVar]) ->
None:
2914 CpSolverSolutionCallback.__init__(self)
2920 """Called on each new solution."""
2921 current_time = time.time()
2924 f
"Solution {self.__solution_count}, time ="
2925 f
" {current_time - self.__start_time:0.2f} s, objective = {obj}"
2928 print(f
" {v} = {self.value(v)}", end=
" ")
2934 """Returns the number of solutions found."""
2939 """Print intermediate solutions (variable values, time)."""
2941 def __init__(self, variables: Sequence[IntVar]) ->
None:
2942 CpSolverSolutionCallback.__init__(self)
2948 """Called on each new solution."""
2949 current_time = time.time()
2951 f
"Solution {self.__solution_count}, time ="
2952 f
" {current_time - self.__start_time:0.2f} s"
2955 print(f
" {v} = {self.value(v)}", end=
" ")
2961 """Returns the number of solutions found."""
2966 """Returns the indices of `obj` as a `pd.Index`."""
2967 if isinstance(obj, pd.Series):
2973 value_or_series: Union[IntegralT, pd.Series], index: pd.Index
2975 """Returns a pd.Series of the given index with the corresponding values.
2978 value_or_series: the values to be converted (if applicable).
2979 index: the index of the resulting pd.Series.
2982 pd.Series: The set of values with the given index.
2985 TypeError: If the type of `value_or_series` is not recognized.
2986 ValueError: If the index does not match.
2988 if isinstance(value_or_series, IntegralTypes):
2989 return pd.Series(data=value_or_series, index=index)
2990 elif isinstance(value_or_series, pd.Series):
2991 if value_or_series.index.equals(index):
2992 return value_or_series
2994 raise ValueError(
"index does not match")
2996 raise TypeError(f
"invalid type={type(value_or_series).__name__!r}")
3000 value_or_series: Union[LinearExprT, pd.Series], index: pd.Index
3002 """Returns a pd.Series of the given index with the corresponding values.
3005 value_or_series: the values to be converted (if applicable).
3006 index: the index of the resulting pd.Series.
3009 pd.Series: The set of values with the given index.
3012 TypeError: If the type of `value_or_series` is not recognized.
3013 ValueError: If the index does not match.
3015 if isinstance(value_or_series, IntegralTypes):
3016 return pd.Series(data=value_or_series, index=index)
3017 elif isinstance(value_or_series, pd.Series):
3018 if value_or_series.index.equals(index):
3019 return value_or_series
3021 raise ValueError(
"index does not match")
3023 raise TypeError(f
"invalid type={type(value_or_series).__name__!r}")
3027 value_or_series: Union[LiteralT, pd.Series], index: pd.Index
3029 """Returns a pd.Series of the given index with the corresponding values.
3032 value_or_series: the values to be converted (if applicable).
3033 index: the index of the resulting pd.Series.
3036 pd.Series: The set of values with the given index.
3039 TypeError: If the type of `value_or_series` is not recognized.
3040 ValueError: If the index does not match.
3042 if isinstance(value_or_series, IntegralTypes):
3043 return pd.Series(data=value_or_series, index=index)
3044 elif isinstance(value_or_series, pd.Series):
3045 if value_or_series.index.equals(index):
3046 return value_or_series
3048 raise ValueError(
"index does not match")
3050 raise TypeError(f
"invalid type={type(value_or_series).__name__!r}")