Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model.py
Go to the documentation of this file.
1# Copyright 2010-2024 Google LLC
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14"""Methods for building and solving CP-SAT models.
15
16The following two sections describe the main
17methods for building and solving CP-SAT models.
18
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.
23
24The following methods implement callbacks that the
25solver calls each time it finds a new solution.
26
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.
36
37Additional methods for solving CP-SAT models:
38
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.
43
44Other methods and functions listed are primarily used for developing OR-Tools,
45rather than for solving specific optimization problems.
46"""
47
48import collections
49import itertools
50import threading
51import time
52from typing import (
53 Any,
54 Callable,
55 Dict,
56 Iterable,
57 List,
58 NoReturn,
59 Optional,
60 Sequence,
61 Tuple,
62 Union,
63 cast,
64 overload,
65)
66import warnings
67
68import numpy as np
69import pandas as pd
70
71from ortools.sat import cp_model_pb2
72from ortools.sat import sat_parameters_pb2
73from ortools.sat.python import cp_model_helper as cmh
74from ortools.sat.python import swig_helper
75from ortools.util.python import sorted_interval_list
76
77Domain = sorted_interval_list.Domain
78
79# The classes below allow linear expressions to be expressed naturally with the
80# usual arithmetic operators + - * / and with constant numbers, which makes the
81# python API very intuitive. See../ samples/*.py for examples.
82
83INT_MIN = -(2**63) # hardcoded to be platform independent.
84INT_MAX = 2**63 - 1
85INT32_MIN = -(2**31)
86INT32_MAX = 2**31 - 1
87
88# CpSolver status (exported to avoid importing cp_model_cp2).
89UNKNOWN = cp_model_pb2.UNKNOWN
90MODEL_INVALID = cp_model_pb2.MODEL_INVALID
91FEASIBLE = cp_model_pb2.FEASIBLE
92INFEASIBLE = cp_model_pb2.INFEASIBLE
93OPTIMAL = cp_model_pb2.OPTIMAL
94
95# Variable selection strategy
96CHOOSE_FIRST = cp_model_pb2.DecisionStrategyProto.CHOOSE_FIRST
97CHOOSE_LOWEST_MIN = cp_model_pb2.DecisionStrategyProto.CHOOSE_LOWEST_MIN
98CHOOSE_HIGHEST_MAX = cp_model_pb2.DecisionStrategyProto.CHOOSE_HIGHEST_MAX
99CHOOSE_MIN_DOMAIN_SIZE = cp_model_pb2.DecisionStrategyProto.CHOOSE_MIN_DOMAIN_SIZE
100CHOOSE_MAX_DOMAIN_SIZE = cp_model_pb2.DecisionStrategyProto.CHOOSE_MAX_DOMAIN_SIZE
101
102# Domain reduction strategy
103SELECT_MIN_VALUE = cp_model_pb2.DecisionStrategyProto.SELECT_MIN_VALUE
104SELECT_MAX_VALUE = cp_model_pb2.DecisionStrategyProto.SELECT_MAX_VALUE
105SELECT_LOWER_HALF = cp_model_pb2.DecisionStrategyProto.SELECT_LOWER_HALF
106SELECT_UPPER_HALF = cp_model_pb2.DecisionStrategyProto.SELECT_UPPER_HALF
107
108# Search branching
109AUTOMATIC_SEARCH = sat_parameters_pb2.SatParameters.AUTOMATIC_SEARCH
110FIXED_SEARCH = sat_parameters_pb2.SatParameters.FIXED_SEARCH
111PORTFOLIO_SEARCH = sat_parameters_pb2.SatParameters.PORTFOLIO_SEARCH
112LP_SEARCH = sat_parameters_pb2.SatParameters.LP_SEARCH
113PSEUDO_COST_SEARCH = sat_parameters_pb2.SatParameters.PSEUDO_COST_SEARCH
114PORTFOLIO_WITH_QUICK_RESTART_SEARCH = (
115 sat_parameters_pb2.SatParameters.PORTFOLIO_WITH_QUICK_RESTART_SEARCH
116)
117HINT_SEARCH = sat_parameters_pb2.SatParameters.HINT_SEARCH
118PARTIAL_FIXED_SEARCH = sat_parameters_pb2.SatParameters.PARTIAL_FIXED_SEARCH
119RANDOMIZED_SEARCH = sat_parameters_pb2.SatParameters.RANDOMIZED_SEARCH
120
121# Type aliases
122IntegralT = Union[int, np.int8, np.uint8, np.int32, np.uint32, np.int64, np.uint64]
123IntegralTypes = (
124 int,
125 np.int8,
126 np.uint8,
127 np.int32,
128 np.uint32,
129 np.int64,
130 np.uint64,
131)
132NumberT = Union[
133 int,
134 float,
135 np.int8,
136 np.uint8,
137 np.int32,
138 np.uint32,
139 np.int64,
140 np.uint64,
141 np.double,
142]
143NumberTypes = (
144 int,
145 float,
146 np.int8,
147 np.uint8,
148 np.int32,
149 np.uint32,
150 np.int64,
151 np.uint64,
152 np.double,
153)
154
155LiteralT = Union["IntVar", "_NotBooleanVariable", IntegralT, bool]
156BoolVarT = Union["IntVar", "_NotBooleanVariable"]
157VariableT = Union["IntVar", IntegralT]
158
159# We need to add 'IntVar' for pytype.
160LinearExprT = Union["LinearExpr", "IntVar", IntegralT]
161ObjLinearExprT = Union["LinearExpr", NumberT]
162BoundedLinearExprT = Union["BoundedLinearExpression", bool]
163
164ArcT = Tuple[IntegralT, IntegralT, LiteralT]
165_IndexOrSeries = Union[pd.Index, pd.Series]
166
167
168def display_bounds(bounds: Sequence[int]) -> str:
169 """Displays a flattened list of intervals."""
170 out = ""
171 for i in range(0, len(bounds), 2):
172 if i != 0:
173 out += ", "
174 if bounds[i] == bounds[i + 1]:
175 out += str(bounds[i])
176 else:
177 out += str(bounds[i]) + ".." + str(bounds[i + 1])
178 return out
179
180
181def short_name(model: cp_model_pb2.CpModelProto, i: int) -> str:
182 """Returns a short name of an integer variable, or its negation."""
183 if i < 0:
184 return "not(%s)" % short_name(model, -i - 1)
185 v = model.variables[i]
186 if v.name:
187 return v.name
188 elif len(v.domain) == 2 and v.domain[0] == v.domain[1]:
189 return str(v.domain[0])
190 else:
191 return "[%s]" % display_bounds(v.domain)
192
193
195 model: cp_model_pb2.CpModelProto, e: cp_model_pb2.LinearExpressionProto
196) -> str:
197 """Pretty-print LinearExpressionProto instances."""
198 if not e.vars:
199 return str(e.offset)
200 if len(e.vars) == 1:
201 var_name = short_name(model, e.vars[0])
202 coeff = e.coeffs[0]
203 result = ""
204 if coeff == 1:
205 result = var_name
206 elif coeff == -1:
207 result = f"-{var_name}"
208 elif coeff != 0:
209 result = f"{coeff} * {var_name}"
210 if e.offset > 0:
211 result = f"{result} + {e.offset}"
212 elif e.offset < 0:
213 result = f"{result} - {-e.offset}"
214 return result
215 # TODO(user): Support more than affine expressions.
216 return str(e)
217
218
220 """Holds an integer linear expression.
221
222 A linear expression is built from integer constants and variables.
223 For example, `x + 2 * (y - z + 1)`.
224
225 Linear expressions are used in CP-SAT models in constraints and in the
226 objective:
227
228 * You can define linear constraints as in:
229
230 ```
231 model.add(x + 2 * y <= 5)
232 model.add(sum(array_of_vars) == 5)
233 ```
234
235 * In CP-SAT, the objective is a linear expression:
236
237 ```
238 model.minimize(x + 2 * y + z)
239 ```
240
241 * For large arrays, using the LinearExpr class is faster that using the python
242 `sum()` function. You can create constraints and the objective from lists of
243 linear expressions or coefficients as follows:
244
245 ```
246 model.minimize(cp_model.LinearExpr.sum(expressions))
247 model.add(cp_model.LinearExpr.weighted_sum(expressions, coefficients) >= 0)
248 ```
249 """
250
251 @classmethod
252 def sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT:
253 """Creates the expression sum(expressions)."""
254 if len(expressions) == 1:
255 return expressions[0]
256 return _SumArray(expressions)
257
258 @overload
259 @classmethod
261 cls,
262 expressions: Sequence[LinearExprT],
263 coefficients: Sequence[IntegralT],
264 ) -> LinearExprT: ...
265
266 @overload
267 @classmethod
269 cls,
270 expressions: Sequence[ObjLinearExprT],
271 coefficients: Sequence[NumberT],
272 ) -> ObjLinearExprT: ...
273
274 @classmethod
275 def weighted_sum(cls, expressions, coefficients):
276 """Creates the expression sum(expressions[i] * coefficients[i])."""
277 if LinearExpr.is_empty_or_all_null(coefficients):
278 return 0
279 elif len(expressions) == 1:
280 return expressions[0] * coefficients[0]
281 else:
282 return _WeightedSum(expressions, coefficients)
283
284 @overload
285 @classmethod
286 def term(
287 cls,
288 expressions: LinearExprT,
289 coefficients: IntegralT,
290 ) -> LinearExprT: ...
291
292 @overload
293 @classmethod
294 def term(
295 cls,
296 expressions: ObjLinearExprT,
297 coefficients: NumberT,
298 ) -> ObjLinearExprT: ...
299
300 @classmethod
301 def term(cls, expression, coefficient):
302 """Creates `expression * coefficient`."""
303 if cmh.is_zero(coefficient):
304 return 0
305 else:
306 return expression * coefficient
307
308 @classmethod
309 def is_empty_or_all_null(cls, coefficients: Sequence[NumberT]) -> bool:
310 for c in coefficients:
311 if not cmh.is_zero(c):
312 return False
313 return True
314
315 @classmethod
317 cls,
318 model: cp_model_pb2.CpModelProto,
319 proto: cp_model_pb2.LinearExpressionProto,
320 ) -> LinearExprT:
321 """Recreate a LinearExpr from a LinearExpressionProto."""
322 offset = proto.offset
323 num_elements = len(proto.vars)
324 if num_elements == 0:
325 return offset
326 elif num_elements == 1:
327 return (
328 IntVar(model, proto.vars[0], None) * proto.coeffs[0] + offset
329 ) # pytype: disable=bad-return-type
330 else:
331 variables = []
332 coeffs = []
333 all_ones = True
334 for index, coeff in zip(proto.vars, proto.coeffs):
335 variables.append(IntVar(model, index, None))
336 coeffs.append(coeff)
337 if not cmh.is_one(coeff):
338 all_ones = False
339 if all_ones:
340 return _SumArray(variables, offset)
341 else:
342 return _WeightedSum(variables, coeffs, offset)
343
344 def get_integer_var_value_map(self) -> Tuple[Dict["IntVar", int], int]:
345 """Scans the expression, and returns (var_coef_map, constant)."""
346 coeffs: Dict["IntVar", int] = collections.defaultdict(int)
347 constant = 0
348 to_process: List[Tuple[LinearExprT, int]] = [(self, 1)]
349 while to_process: # Flatten to avoid recursion.
350 expr: LinearExprT
351 coeff: int
352 expr, coeff = to_process.pop()
353 if isinstance(expr, IntegralTypes):
354 constant += coeff * int(expr)
355 elif isinstance(expr, _ProductCst):
356 to_process.append((expr.expression(), coeff * expr.coefficient()))
357 elif isinstance(expr, _Sum):
358 to_process.append((expr.left(), coeff))
359 to_process.append((expr.right(), coeff))
360 elif isinstance(expr, _SumArray):
361 for e in expr.expressions():
362 to_process.append((e, coeff))
363 constant += expr.constant() * coeff
364 elif isinstance(expr, _WeightedSum):
365 for e, c in zip(expr.expressions(), expr.coefficients()):
366 to_process.append((e, coeff * c))
367 constant += expr.constant() * coeff
368 elif isinstance(expr, IntVar):
369 coeffs[expr] += coeff
370 elif isinstance(expr, _NotBooleanVariable):
371 constant += coeff
372 coeffs[expr.negated()] -= coeff
373 elif isinstance(expr, NumberTypes):
374 raise TypeError(
375 f"Floating point constants are not supported in constraints: {expr}"
376 )
377 else:
378 raise TypeError("Unrecognized linear expression: " + str(expr))
379
380 return coeffs, constant
381
383 self,
384 ) -> Tuple[Dict["IntVar", float], float, bool]:
385 """Scans the expression. Returns (var_coef_map, constant, is_integer)."""
386 coeffs: Dict["IntVar", Union[int, float]] = {}
387 constant: Union[int, float] = 0
388 to_process: List[Tuple[LinearExprT, Union[int, float]]] = [(self, 1)]
389 while to_process: # Flatten to avoid recursion.
390 expr, coeff = to_process.pop()
391 if isinstance(expr, IntegralTypes): # Keep integrality.
392 constant += coeff * int(expr)
393 elif isinstance(expr, NumberTypes):
394 constant += coeff * float(expr)
395 elif isinstance(expr, _ProductCst):
396 to_process.append((expr.expression(), coeff * expr.coefficient()))
397 elif isinstance(expr, _Sum):
398 to_process.append((expr.left(), coeff))
399 to_process.append((expr.right(), coeff))
400 elif isinstance(expr, _SumArray):
401 for e in expr.expressions():
402 to_process.append((e, coeff))
403 constant += expr.constant() * coeff
404 elif isinstance(expr, _WeightedSum):
405 for e, c in zip(expr.expressions(), expr.coefficients()):
406 to_process.append((e, coeff * c))
407 constant += expr.constant() * coeff
408 elif isinstance(expr, IntVar):
409 if expr in coeffs:
410 coeffs[expr] += coeff
411 else:
412 coeffs[expr] = coeff
413 elif isinstance(expr, _NotBooleanVariable):
414 constant += coeff
415 if expr.negated() in coeffs:
416 coeffs[expr.negated()] -= coeff
417 else:
418 coeffs[expr.negated()] = -coeff
419 else:
420 raise TypeError("Unrecognized linear expression: " + str(expr))
421 is_integer = isinstance(constant, IntegralTypes)
422 if is_integer:
423 for coeff in coeffs.values():
424 if not isinstance(coeff, IntegralTypes):
425 is_integer = False
426 break
427 return coeffs, constant, is_integer
428
429 def __hash__(self) -> int:
430 return object.__hash__(self)
431
432 def __abs__(self) -> NoReturn:
433 raise NotImplementedError(
434 "calling abs() on a linear expression is not supported, "
435 "please use CpModel.add_abs_equality"
436 )
437
438 @overload
439 def __add__(self, arg: "LinearExpr") -> "LinearExpr": ...
440
441 @overload
442 def __add__(self, arg: NumberT) -> "LinearExpr": ...
443
444 def __add__(self, arg):
445 if cmh.is_zero(arg):
446 return self
447 return _Sum(self, arg)
448
449 @overload
450 def __radd__(self, arg: "LinearExpr") -> "LinearExpr": ...
451
452 @overload
453 def __radd__(self, arg: NumberT) -> "LinearExpr": ...
454
455 def __radd__(self, arg):
456 return self.__add____add____add__(arg)
457
458 @overload
459 def __sub__(self, arg: "LinearExpr") -> "LinearExpr": ...
460
461 @overload
462 def __sub__(self, arg: NumberT) -> "LinearExpr": ...
463
464 def __sub__(self, arg):
465 if cmh.is_zero(arg):
466 return self
467 if isinstance(arg, NumberTypes):
468 arg = cmh.assert_is_a_number(arg)
469 return _Sum(self, -arg)
470 else:
471 return _Sum(self, -arg)
472
473 @overload
474 def __rsub__(self, arg: "LinearExpr") -> "LinearExpr": ...
475
476 @overload
477 def __rsub__(self, arg: NumberT) -> "LinearExpr": ...
478
479 def __rsub__(self, arg):
480 return _Sum(-self, arg)
481
482 @overload
483 def __mul__(self, arg: IntegralT) -> Union["LinearExpr", IntegralT]: ...
484
485 @overload
486 def __mul__(self, arg: NumberT) -> Union["LinearExpr", NumberT]: ...
487
488 def __mul__(self, arg):
489 arg = cmh.assert_is_a_number(arg)
490 if cmh.is_one(arg):
491 return self
492 elif cmh.is_zero(arg):
493 return 0
494 return _ProductCst(self, arg)
495
496 @overload
497 def __rmul__(self, arg: IntegralT) -> Union["LinearExpr", IntegralT]: ...
498
499 @overload
500 def __rmul__(self, arg: NumberT) -> Union["LinearExpr", NumberT]: ...
501
502 def __rmul__(self, arg):
503 return self.__mul____mul____mul__(arg)
504
505 def __div__(self, _) -> NoReturn:
506 raise NotImplementedError(
507 "calling / on a linear expression is not supported, "
508 "please use CpModel.add_division_equality"
509 )
510
511 def __truediv__(self, _) -> NoReturn:
512 raise NotImplementedError(
513 "calling // on a linear expression is not supported, "
514 "please use CpModel.add_division_equality"
515 )
516
517 def __mod__(self, _) -> NoReturn:
518 raise NotImplementedError(
519 "calling %% on a linear expression is not supported, "
520 "please use CpModel.add_modulo_equality"
521 )
522
523 def __pow__(self, _) -> NoReturn:
524 raise NotImplementedError(
525 "calling ** on a linear expression is not supported, "
526 "please use CpModel.add_multiplication_equality"
527 )
528
529 def __lshift__(self, _) -> NoReturn:
530 raise NotImplementedError(
531 "calling left shift on a linear expression is not supported"
532 )
533
534 def __rshift__(self, _) -> NoReturn:
535 raise NotImplementedError(
536 "calling right shift on a linear expression is not supported"
537 )
538
539 def __and__(self, _) -> NoReturn:
540 raise NotImplementedError(
541 "calling and on a linear expression is not supported, "
542 "please use CpModel.add_bool_and"
543 )
544
545 def __or__(self, _) -> NoReturn:
546 raise NotImplementedError(
547 "calling or on a linear expression is not supported, "
548 "please use CpModel.add_bool_or"
549 )
550
551 def __xor__(self, _) -> NoReturn:
552 raise NotImplementedError(
553 "calling xor on a linear expression is not supported, "
554 "please use CpModel.add_bool_xor"
555 )
556
557 def __neg__(self) -> "LinearExpr":
558 return _ProductCst(self, -1)
559
560 def __bool__(self) -> NoReturn:
561 raise NotImplementedError(
562 "Evaluating a LinearExpr instance as a Boolean is not implemented."
563 )
564
565 def __eq__(self, arg: LinearExprT) -> BoundedLinearExprT: # type: ignore[override]
566 if arg is None:
567 return False
568 if isinstance(arg, IntegralTypes):
569 arg = cmh.assert_is_int64(arg)
570 return BoundedLinearExpression(self, [arg, arg])
571 elif isinstance(arg, LinearExpr):
572 return BoundedLinearExpression(self - arg, [0, 0])
573 else:
574 return False
575
576 def __ge__(self, arg: LinearExprT) -> "BoundedLinearExpression":
577 if isinstance(arg, IntegralTypes):
578 arg = cmh.assert_is_int64(arg)
579 return BoundedLinearExpression(self, [arg, INT_MAX])
580 else:
581 return BoundedLinearExpression(self - arg, [0, INT_MAX])
582
583 def __le__(self, arg: LinearExprT) -> "BoundedLinearExpression":
584 if isinstance(arg, IntegralTypes):
585 arg = cmh.assert_is_int64(arg)
586 return BoundedLinearExpression(self, [INT_MIN, arg])
587 else:
588 return BoundedLinearExpression(self - arg, [INT_MIN, 0])
589
590 def __lt__(self, arg: LinearExprT) -> "BoundedLinearExpression":
591 if isinstance(arg, IntegralTypes):
592 arg = cmh.assert_is_int64(arg)
593 if arg == INT_MIN:
594 raise ArithmeticError("< INT_MIN is not supported")
595 return BoundedLinearExpression(self, [INT_MIN, arg - 1])
596 else:
597 return BoundedLinearExpression(self - arg, [INT_MIN, -1])
598
599 def __gt__(self, arg: LinearExprT) -> "BoundedLinearExpression":
600 if isinstance(arg, IntegralTypes):
601 arg = cmh.assert_is_int64(arg)
602 if arg == INT_MAX:
603 raise ArithmeticError("> INT_MAX is not supported")
604 return BoundedLinearExpression(self, [arg + 1, INT_MAX])
605 else:
606 return BoundedLinearExpression(self - arg, [1, INT_MAX])
607
608 def __ne__(self, arg: LinearExprT) -> BoundedLinearExprT: # type: ignore[override]
609 if arg is None:
610 return True
611 if isinstance(arg, IntegralTypes):
612 arg = cmh.assert_is_int64(arg)
613 if arg == INT_MAX:
614 return BoundedLinearExpression(self, [INT_MIN, INT_MAX - 1])
615 elif arg == INT_MIN:
616 return BoundedLinearExpression(self, [INT_MIN + 1, INT_MAX])
617 else:
619 self, [INT_MIN, arg - 1, arg + 1, INT_MAX]
620 )
621 elif isinstance(arg, LinearExpr):
622 return BoundedLinearExpression(self - arg, [INT_MIN, -1, 1, INT_MAX])
623 else:
624 return True
625
626 # Compatibility with pre PEP8
627 # pylint: disable=invalid-name
628 @classmethod
629 def Sum(cls, expressions: Sequence[LinearExprT]) -> LinearExprT:
630 """Creates the expression sum(expressions)."""
631 return cls.sum(expressions)
632
633 @overload
634 @classmethod
636 cls,
637 expressions: Sequence[LinearExprT],
638 coefficients: Sequence[IntegralT],
639 ) -> LinearExprT: ...
640
641 @overload
642 @classmethod
644 cls,
645 expressions: Sequence[ObjLinearExprT],
646 coefficients: Sequence[NumberT],
647 ) -> ObjLinearExprT: ...
648
649 @classmethod
650 def WeightedSum(cls, expressions, coefficients):
651 """Creates the expression sum(expressions[i] * coefficients[i])."""
652 return cls.weighted_sumweighted_sumweighted_sum(expressions, coefficients)
653
654 @overload
655 @classmethod
656 def Term(
657 cls,
658 expressions: LinearExprT,
659 coefficients: IntegralT,
660 ) -> LinearExprT: ...
661
662 @overload
663 @classmethod
664 def Term(
665 cls,
666 expressions: ObjLinearExprT,
667 coefficients: NumberT,
668 ) -> ObjLinearExprT: ...
669
670 @classmethod
671 def Term(cls, expression, coefficient):
672 """Creates `expression * coefficient`."""
673 return cls.termtermterm(expression, coefficient)
674
675 # pylint: enable=invalid-name
676
677
679 """Represents the sum of two LinearExprs."""
680
681 def __init__(self, left, right) -> None:
682 for x in [left, right]:
683 if not isinstance(x, (NumberTypes, LinearExpr)):
684 raise TypeError("not an linear expression: " + str(x))
685 self.__left = left
686 self.__right = right
687
688 def left(self):
689 return self.__left
690
691 def right(self):
692 return self.__right
693
694 def __str__(self):
695 return f"({self.__left} + {self.__right})"
696
697 def __repr__(self):
698 return f"sum({self.__left!r}, {self.__right!r})"
699
700
702 """Represents the product of a LinearExpr by a constant."""
703
704 def __init__(self, expr, coeff) -> None:
705 coeff = cmh.assert_is_a_number(coeff)
706 if isinstance(expr, _ProductCst):
707 self.__expr = expr.expression()
708 self.__coef = expr.coefficient() * coeff
709 else:
710 self.__expr = expr
711 self.__coef = coeff
712
713 def __str__(self):
714 if self.__coef == -1:
715 return "-" + str(self.__expr)
716 else:
717 return "(" + str(self.__coef) + " * " + str(self.__expr) + ")"
718
719 def __repr__(self):
720 return f"ProductCst({self.__expr!r}, {self.__coef!r})"
721
722 def coefficient(self):
723 return self.__coef
724
725 def expression(self):
726 return self.__expr
727
728
730 """Represents the sum of a list of LinearExpr and a constant."""
731
732 def __init__(self, expressions, constant=0) -> None:
733 self.__expressions = []
734 self.__constant = constant
735 for x in expressions:
736 if isinstance(x, NumberTypes):
737 if cmh.is_zero(x):
738 continue
739 x = cmh.assert_is_a_number(x)
740 self.__constant += x
741 elif isinstance(x, LinearExpr):
742 self.__expressions.append(x)
743 else:
744 raise TypeError("not an linear expression: " + str(x))
745
746 def __str__(self):
747 constant_terms = (self.__constant,) if self.__constant != 0 else ()
748 exprs_str = " + ".join(
749 map(repr, itertools.chain(self.__expressions, constant_terms))
750 )
751 if not exprs_str:
752 return "0"
753 return f"({exprs_str})"
754
755 def __repr__(self):
756 exprs_str = ", ".join(map(repr, self.__expressions))
757 return f"SumArray({exprs_str}, {self.__constant})"
758
759 def expressions(self):
760 return self.__expressions
761
762 def constant(self):
763 return self.__constant
764
765
767 """Represents sum(ai * xi) + b."""
768
769 def __init__(self, expressions, coefficients, constant=0) -> None:
770 self.__expressions = []
771 self.__coefficients = []
772 self.__constant = constant
773 if len(expressions) != len(coefficients):
774 raise TypeError(
775 "In the LinearExpr.weighted_sum method, the expression array and the "
776 " coefficient array must have the same length."
777 )
778 for e, c in zip(expressions, coefficients):
779 c = cmh.assert_is_a_number(c)
780 if cmh.is_zero(c):
781 continue
782 if isinstance(e, NumberTypes):
783 e = cmh.assert_is_a_number(e)
784 self.__constant += e * c
785 elif isinstance(e, LinearExpr):
786 self.__expressions.append(e)
787 self.__coefficients.append(c)
788 else:
789 raise TypeError("not an linear expression: " + str(e))
790
791 def __str__(self):
792 output = None
793 for expr, coeff in zip(self.__expressions, self.__coefficients):
794 if not output and cmh.is_one(coeff):
795 output = str(expr)
796 elif not output and cmh.is_minus_one(coeff):
797 output = "-" + str(expr)
798 elif not output:
799 output = f"{coeff} * {expr}"
800 elif cmh.is_one(coeff):
801 output += f" + {expr}"
802 elif cmh.is_minus_one(coeff):
803 output += f" - {expr}"
804 elif coeff > 1:
805 output += f" + {coeff} * {expr}"
806 elif coeff < -1:
807 output += f" - {-coeff} * {expr}"
808 if output is None:
809 output = str(self.__constant)
810 elif self.__constant > 0:
811 output += f" + {self.__constant}"
812 elif self.__constant < 0:
813 output += f" - {-self.__constant}"
814 return output
815
816 def __repr__(self):
817 return (
818 f"weighted_sum({self.__expressions!r}, {self.__coefficients!r},"
819 f" {self.__constant})"
820 )
821
822 def expressions(self):
823 return self.__expressions
824
825 def coefficients(self):
826 return self.__coefficients
827
828 def constant(self):
829 return self.__constant
830
831
833 """An integer variable.
834
835 An IntVar is an object that can take on any integer value within defined
836 ranges. Variables appear in constraint like:
837
838 x + y >= 5
839 AllDifferent([x, y, z])
840
841 Solving a model is equivalent to finding, for each variable, a single value
842 from the set of initial values (called the initial domain), such that the
843 model is feasible, or optimal if you provided an objective function.
844 """
845
847 self,
848 model: cp_model_pb2.CpModelProto,
849 domain: Union[int, sorted_interval_list.Domain],
850 name: Optional[str],
851 ) -> None:
852 """See CpModel.new_int_var below."""
853 self.__index: int
854 self.__var__var: cp_model_pb2.IntegerVariableProto
855 self.__negation: Optional[_NotBooleanVariable] = None
856 # Python do not support multiple __init__ methods.
857 # This method is only called from the CpModel class.
858 # We hack the parameter to support the two cases:
859 # case 1:
860 # model is a CpModelProto, domain is a Domain, and name is a string.
861 # case 2:
862 # model is a CpModelProto, domain is an index (int), and name is None.
863 if isinstance(domain, IntegralTypes) and name is None:
864 self.__index = int(domain)
865 self.__var__var = model.variables[domain]
866 else:
867 self.__index = len(model.variables)
868 self.__var__var = model.variables.add()
869 self.__var__var.domain.extend(
870 cast(sorted_interval_list.Domain, domain).flattened_intervals()
871 )
872 if name is not None:
873 self.__var__var.name = name
874
875 @property
876 def index(self) -> int:
877 """Returns the index of the variable in the model."""
878 return self.__index
879
880 @property
881 def proto(self) -> cp_model_pb2.IntegerVariableProto:
882 """Returns the variable protobuf."""
883 return self.__var__var
884
885 def is_equal_to(self, other: Any) -> bool:
886 """Returns true if self == other in the python sense."""
887 if not isinstance(other, IntVar):
888 return False
889 return self.indexindex == other.index
890
891 def __str__(self) -> str:
892 if not self.__var__var.name:
893 if (
894 len(self.__var__var.domain) == 2
895 and self.__var__var.domain[0] == self.__var__var.domain[1]
896 ):
897 # Special case for constants.
898 return str(self.__var__var.domain[0])
899 else:
900 return "unnamed_var_%i" % self.__index
901 return self.__var__var.name
902
903 def __repr__(self) -> str:
904 return "%s(%s)" % (self.__var__var.name, display_bounds(self.__var__var.domain))
905
906 @property
907 def name(self) -> str:
908 if not self.__var__var or not self.__var__var.name:
909 return ""
910 return self.__var__var.name
911
912 def negated(self) -> "_NotBooleanVariable":
913 """Returns the negation of a Boolean variable.
914
915 This method implements the logical negation of a Boolean variable.
916 It is only valid if the variable has a Boolean domain (0 or 1).
917
918 Note that this method is nilpotent: `x.negated().negated() == x`.
919 """
920
921 for bound in self.__var__var.domain:
922 if bound < 0 or bound > 1:
923 raise TypeError(
924 f"cannot call negated on a non boolean variable: {self}"
925 )
926 if self.__negation is None:
928 return self.__negation
929
930 def __invert__(self) -> "_NotBooleanVariable":
931 """Returns the logical negation of a Boolean variable."""
932 return self.negated()
933
934 # Pre PEP8 compatibility.
935 # pylint: disable=invalid-name
936 Not = negated
937
938 def Name(self) -> str:
939 return self.name
940
941 def Proto(self) -> cp_model_pb2.IntegerVariableProto:
942 return self.proto
943
944 def Index(self) -> int:
945 return self.indexindex
946
947 # pylint: enable=invalid-name
948
949
951 """Negation of a boolean variable."""
952
953 def __init__(self, boolvar: IntVar) -> None:
954 self.__boolvar: IntVar = boolvar
955
956 @property
957 def index(self) -> int:
958 return -self.__boolvar.index - 1
959
960 def negated(self) -> IntVar:
961 return self.__boolvar
962
963 def __invert__(self) -> IntVar:
964 """Returns the logical negation of a Boolean literal."""
965 return self.negated()
966
967 def __str__(self) -> str:
968 return self.name
969
970 @property
971 def name(self) -> str:
972 return "not(%s)" % str(self.__boolvar)
973
974 def __bool__(self) -> NoReturn:
975 raise NotImplementedError(
976 "Evaluating a literal as a Boolean value is not implemented."
977 )
978
979 # Pre PEP8 compatibility.
980 # pylint: disable=invalid-name
981 def Not(self) -> "IntVar":
982 return self.negated()
983
984 def Index(self) -> int:
985 return self.index
986
987 # pylint: enable=invalid-name
988
989
991 """Represents a linear constraint: `lb <= linear expression <= ub`.
992
993 The only use of this class is to be added to the CpModel through
994 `CpModel.add(expression)`, as in:
995
996 model.add(x + 2 * y -1 >= z)
997 """
998
999 def __init__(self, expr: LinearExprT, bounds: Sequence[int]) -> None:
1000 self.__expr: LinearExprT = expr
1001 self.__bounds__bounds: Sequence[int] = bounds
1002
1003 def __str__(self):
1004 if len(self.__bounds__bounds) == 2:
1005 lb, ub = self.__bounds__bounds
1006 if lb > INT_MIN and ub < INT_MAX:
1007 if lb == ub:
1008 return str(self.__expr) + " == " + str(lb)
1009 else:
1010 return str(lb) + " <= " + str(self.__expr) + " <= " + str(ub)
1011 elif lb > INT_MIN:
1012 return str(self.__expr) + " >= " + str(lb)
1013 elif ub < INT_MAX:
1014 return str(self.__expr) + " <= " + str(ub)
1015 else:
1016 return "True (unbounded expr " + str(self.__expr) + ")"
1017 elif (
1018 len(self.__bounds__bounds) == 4
1019 and self.__bounds__bounds[0] == INT_MIN
1020 and self.__bounds__bounds[1] + 2 == self.__bounds__bounds[2]
1021 and self.__bounds__bounds[3] == INT_MAX
1022 ):
1023 return str(self.__expr) + " != " + str(self.__bounds__bounds[1] + 1)
1024 else:
1025 return str(self.__expr) + " in [" + display_bounds(self.__bounds__bounds) + "]"
1026
1027 def expression(self) -> LinearExprT:
1028 return self.__expr
1029
1030 def bounds(self) -> Sequence[int]:
1031 return self.__bounds__bounds
1032
1033 def __bool__(self) -> bool:
1034 expr = self.__expr
1035 if isinstance(expr, LinearExpr):
1036 coeffs_map, constant = expr.get_integer_var_value_map()
1037 all_coeffs = set(coeffs_map.values())
1038 same_var = set([0])
1039 eq_bounds = [0, 0]
1040 different_vars = set([-1, 1])
1041 ne_bounds = [INT_MIN, -1, 1, INT_MAX]
1042 if (
1043 len(coeffs_map) == 1
1044 and all_coeffs == same_var
1045 and constant == 0
1046 and (self.__bounds__bounds == eq_bounds or self.__bounds__bounds == ne_bounds)
1047 ):
1048 return self.__bounds__bounds == eq_bounds
1049 if (
1050 len(coeffs_map) == 2
1051 and all_coeffs == different_vars
1052 and constant == 0
1053 and (self.__bounds__bounds == eq_bounds or self.__bounds__bounds == ne_bounds)
1054 ):
1055 return self.__bounds__bounds == ne_bounds
1056
1057 raise NotImplementedError(
1058 f'Evaluating a BoundedLinearExpression "{self}" as a Boolean value'
1059 + " is not supported."
1060 )
1061
1062
1064 """Base class for constraints.
1065
1066 Constraints are built by the CpModel through the add<XXX> methods.
1067 Once created by the CpModel class, they are automatically added to the model.
1068 The purpose of this class is to allow specification of enforcement literals
1069 for this constraint.
1070
1071 b = model.new_bool_var('b')
1072 x = model.new_int_var(0, 10, 'x')
1073 y = model.new_int_var(0, 10, 'y')
1074
1075 model.add(x + 2 * y == 5).only_enforce_if(b.negated())
1076 """
1077
1079 self,
1080 cp_model: "CpModel",
1081 ) -> None:
1082 self.__index: int = len(cp_model.proto.constraints)
1083 self.__cp_model: "CpModel" = cp_model
1084 self.__constraint: cp_model_pb2.ConstraintProto = (
1085 cp_model.proto.constraints.add()
1086 )
1087
1088 @overload
1089 def only_enforce_if(self, boolvar: Iterable[LiteralT]) -> "Constraint": ...
1090
1091 @overload
1092 def only_enforce_if(self, *boolvar: LiteralT) -> "Constraint": ...
1093
1094 def only_enforce_if(self, *boolvar) -> "Constraint":
1095 """Adds an enforcement literal to the constraint.
1096
1097 This method adds one or more literals (that is, a boolean variable or its
1098 negation) as enforcement literals. The conjunction of all these literals
1099 determines whether the constraint is active or not. It acts as an
1100 implication, so if the conjunction is true, it implies that the constraint
1101 must be enforced. If it is false, then the constraint is ignored.
1102
1103 BoolOr, BoolAnd, and linear constraints all support enforcement literals.
1104
1105 Args:
1106 *boolvar: One or more Boolean literals.
1107
1108 Returns:
1109 self.
1110 """
1111 for lit in expand_generator_or_tuple(boolvar):
1112 if (cmh.is_boolean(lit) and lit) or (
1113 isinstance(lit, IntegralTypes) and lit == 1
1114 ):
1115 # Always true. Do nothing.
1116 pass
1117 elif (cmh.is_boolean(lit) and not lit) or (
1118 isinstance(lit, IntegralTypes) and lit == 0
1119 ):
1120 self.__constraint.enforcement_literal.append(
1121 self.__cp_model.new_constant(0).index
1122 )
1123 else:
1124 self.__constraint.enforcement_literal.append(
1125 cast(Union[IntVar, _NotBooleanVariable], lit).index
1126 )
1127 return self
1128
1129 def with_name(self, name: str) -> "Constraint":
1130 """Sets the name of the constraint."""
1131 if name:
1132 self.__constraint.name = name
1133 else:
1134 self.__constraint.ClearField("name")
1135 return self
1136
1137 @property
1138 def name(self) -> str:
1139 """Returns the name of the constraint."""
1140 if not self.__constraint or not self.__constraint.name:
1141 return ""
1142 return self.__constraint.name
1143
1144 @property
1145 def index(self) -> int:
1146 """Returns the index of the constraint in the model."""
1147 return self.__index
1148
1149 @property
1150 def proto(self) -> cp_model_pb2.ConstraintProto:
1151 """Returns the constraint protobuf."""
1152 return self.__constraint
1153
1154 # Pre PEP8 compatibility.
1155 # pylint: disable=invalid-name
1156 OnlyEnforceIf = only_enforce_if
1157 WithName = with_name
1158
1159 def Name(self) -> str:
1160 return self.name
1161
1162 def Index(self) -> int:
1163 return self.index
1164
1165 def Proto(self) -> cp_model_pb2.ConstraintProto:
1166 return self.proto
1167
1168 # pylint: enable=invalid-name
1169
1170
1172 """Represents an Interval variable.
1173
1174 An interval variable is both a constraint and a variable. It is defined by
1175 three integer variables: start, size, and end.
1176
1177 It is a constraint because, internally, it enforces that start + size == end.
1178
1179 It is also a variable as it can appear in specific scheduling constraints:
1180 NoOverlap, NoOverlap2D, Cumulative.
1181
1182 Optionally, an enforcement literal can be added to this constraint, in which
1183 case these scheduling constraints will ignore interval variables with
1184 enforcement literals assigned to false. Conversely, these constraints will
1185 also set these enforcement literals to false if they cannot fit these
1186 intervals into the schedule.
1187
1188 Raises:
1189 ValueError: if start, size, end are not defined, or have the wrong type.
1190 """
1191
1193 self,
1194 model: cp_model_pb2.CpModelProto,
1195 start: Union[cp_model_pb2.LinearExpressionProto, int],
1196 size: Optional[cp_model_pb2.LinearExpressionProto],
1197 end: Optional[cp_model_pb2.LinearExpressionProto],
1198 is_present_index: Optional[int],
1199 name: Optional[str],
1200 ) -> None:
1201 self.__model: cp_model_pb2.CpModelProto = model
1202 self.__index: int
1203 self.__ct__ct: cp_model_pb2.ConstraintProto
1204 # As with the IntVar::__init__ method, we hack the __init__ method to
1205 # support two use cases:
1206 # case 1: called when creating a new interval variable.
1207 # {start|size|end} are linear expressions, is_present_index is either
1208 # None or the index of a Boolean literal. name is a string
1209 # case 2: called when querying an existing interval variable.
1210 # start_index is an int, all parameters after are None.
1211 if isinstance(start, int):
1212 if size is not None:
1213 raise ValueError("size should be None")
1214 if end is not None:
1215 raise ValueError("end should be None")
1216 if is_present_index is not None:
1217 raise ValueError("is_present_index should be None")
1218 self.__index = cast(int, start)
1219 self.__ct__ct = model.constraints[self.__index]
1220 else:
1221 self.__index = len(model.constraints)
1222 self.__ct__ct = self.__model.constraints.add()
1223 if start is None:
1224 raise TypeError("start is not defined")
1225 self.__ct__ct.interval.start.CopyFrom(start)
1226 if size is None:
1227 raise TypeError("size is not defined")
1228 self.__ct__ct.interval.size.CopyFrom(size)
1229 if end is None:
1230 raise TypeError("end is not defined")
1231 self.__ct__ct.interval.end.CopyFrom(end)
1232 if is_present_index is not None:
1233 self.__ct__ct.enforcement_literal.append(is_present_index)
1234 if name:
1235 self.__ct__ct.name = name
1236
1237 @property
1238 def index(self) -> int:
1239 """Returns the index of the interval constraint in the model."""
1240 return self.__index
1241
1242 @property
1243 def proto(self) -> cp_model_pb2.IntervalConstraintProto:
1244 """Returns the interval protobuf."""
1245 return self.__ct__ct.interval
1246
1247 def __str__(self):
1248 return self.__ct__ct.name
1249
1250 def __repr__(self):
1251 interval = self.__ct__ct.interval
1252 if self.__ct__ct.enforcement_literal:
1253 return "%s(start = %s, size = %s, end = %s, is_present = %s)" % (
1254 self.__ct__ct.name,
1255 short_expr_name(self.__model, interval.start),
1256 short_expr_name(self.__model, interval.size),
1257 short_expr_name(self.__model, interval.end),
1258 short_name(self.__model, self.__ct__ct.enforcement_literal[0]),
1259 )
1260 else:
1261 return "%s(start = %s, size = %s, end = %s)" % (
1262 self.__ct__ct.name,
1263 short_expr_name(self.__model, interval.start),
1264 short_expr_name(self.__model, interval.size),
1265 short_expr_name(self.__model, interval.end),
1266 )
1267
1268 @property
1269 def name(self) -> str:
1270 if not self.__ct__ct or not self.__ct__ct.name:
1271 return ""
1272 return self.__ct__ct.name
1273
1274 def start_expr(self) -> LinearExprT:
1275 return LinearExpr.rebuild_from_linear_expression_proto(
1276 self.__model, self.__ct__ct.interval.start
1277 )
1278
1279 def size_expr(self) -> LinearExprT:
1280 return LinearExpr.rebuild_from_linear_expression_proto(
1281 self.__model, self.__ct__ct.interval.size
1282 )
1283
1284 def end_expr(self) -> LinearExprT:
1285 return LinearExpr.rebuild_from_linear_expression_proto(
1286 self.__model, self.__ct__ct.interval.end
1287 )
1288
1289 # Pre PEP8 compatibility.
1290 # pylint: disable=invalid-name
1291 def Name(self) -> str:
1292 return self.name
1293
1294 def Index(self) -> int:
1295 return self.index
1296
1297 def Proto(self) -> cp_model_pb2.IntervalConstraintProto:
1298 return self.proto
1299
1300 StartExpr = start_expr
1301 SizeExpr = size_expr
1302 EndExpr = end_expr
1303
1304 # pylint: enable=invalid-name
1305
1306
1307def object_is_a_true_literal(literal: LiteralT) -> bool:
1308 """Checks if literal is either True, or a Boolean literals fixed to True."""
1309 if isinstance(literal, IntVar):
1310 proto = literal.proto
1311 return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1
1312 if isinstance(literal, _NotBooleanVariable):
1313 proto = literal.negated().proto
1314 return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0
1315 if isinstance(literal, IntegralTypes):
1316 return int(literal) == 1
1317 return False
1318
1319
1320def object_is_a_false_literal(literal: LiteralT) -> bool:
1321 """Checks if literal is either False, or a Boolean literals fixed to False."""
1322 if isinstance(literal, IntVar):
1323 proto = literal.proto
1324 return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0
1325 if isinstance(literal, _NotBooleanVariable):
1326 proto = literal.negated().proto
1327 return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1
1328 if isinstance(literal, IntegralTypes):
1329 return int(literal) == 0
1330 return False
1331
1332
1334 """Methods for building a CP model.
1335
1336 Methods beginning with:
1337
1338 * ```New``` create integer, boolean, or interval variables.
1339 * ```add``` create new constraints and add them to the model.
1340 """
1341
1342 def __init__(self) -> None:
1343 self.__model: cp_model_pb2.CpModelProto = cp_model_pb2.CpModelProto()
1344 self.__constant_map: Dict[IntegralT, int] = {}
1345
1346 # Naming.
1347 @property
1348 def name(self) -> str:
1349 """Returns the name of the model."""
1350 if not self.__model or not self.__model.name:
1351 return ""
1352 return self.__model.name
1353
1354 @name.setter
1355 def name(self, name: str):
1356 """Sets the name of the model."""
1357 self.__model.name = name
1358
1359 # Integer variable.
1360
1361 def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar:
1362 """Create an integer variable with domain [lb, ub].
1363
1364 The CP-SAT solver is limited to integer variables. If you have fractional
1365 values, scale them up so that they become integers; if you have strings,
1366 encode them as integers.
1367
1368 Args:
1369 lb: Lower bound for the variable.
1370 ub: Upper bound for the variable.
1371 name: The name of the variable.
1372
1373 Returns:
1374 a variable whose domain is [lb, ub].
1375 """
1376
1377 return IntVar(self.__model, sorted_interval_list.Domain(lb, ub), name)
1378
1380 self, domain: sorted_interval_list.Domain, name: str
1381 ) -> IntVar:
1382 """Create an integer variable from a domain.
1383
1384 A domain is a set of integers specified by a collection of intervals.
1385 For example, `model.new_int_var_from_domain(cp_model.
1386 Domain.from_intervals([[1, 2], [4, 6]]), 'x')`
1387
1388 Args:
1389 domain: An instance of the Domain class.
1390 name: The name of the variable.
1391
1392 Returns:
1393 a variable whose domain is the given domain.
1394 """
1395 return IntVar(self.__model, domain, name)
1396
1397 def new_bool_var(self, name: str) -> IntVar:
1398 """Creates a 0-1 variable with the given name."""
1399 return IntVar(self.__model, sorted_interval_list.Domain(0, 1), name)
1400
1401 def new_constant(self, value: IntegralT) -> IntVar:
1402 """Declares a constant integer."""
1403 return IntVar(self.__model, self.get_or_make_index_from_constant(value), None)
1404
1406 self,
1407 name: str,
1408 index: pd.Index,
1409 lower_bounds: Union[IntegralT, pd.Series],
1410 upper_bounds: Union[IntegralT, pd.Series],
1411 ) -> pd.Series:
1412 """Creates a series of (scalar-valued) variables with the given name.
1413
1414 Args:
1415 name (str): Required. The name of the variable set.
1416 index (pd.Index): Required. The index to use for the variable set.
1417 lower_bounds (Union[int, pd.Series]): A lower bound for variables in the
1418 set. If a `pd.Series` is passed in, it will be based on the
1419 corresponding values of the pd.Series.
1420 upper_bounds (Union[int, pd.Series]): An upper bound for variables in the
1421 set. If a `pd.Series` is passed in, it will be based on the
1422 corresponding values of the pd.Series.
1423
1424 Returns:
1425 pd.Series: The variable set indexed by its corresponding dimensions.
1426
1427 Raises:
1428 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1429 ValueError: if the `name` is not a valid identifier or already exists.
1430 ValueError: if the `lowerbound` is greater than the `upperbound`.
1431 ValueError: if the index of `lower_bound`, or `upper_bound` does not match
1432 the input index.
1433 """
1434 if not isinstance(index, pd.Index):
1435 raise TypeError("Non-index object is used as index")
1436 if not name.isidentifier():
1437 raise ValueError("name={} is not a valid identifier".format(name))
1438 if (
1439 isinstance(lower_bounds, IntegralTypes)
1440 and isinstance(upper_bounds, IntegralTypes)
1441 and lower_bounds > upper_bounds
1442 ):
1443 raise ValueError(
1444 f"lower_bound={lower_bounds} is greater than"
1445 f" upper_bound={upper_bounds} for variable set={name}"
1446 )
1447
1449 lower_bounds, index
1450 )
1452 upper_bounds, index
1453 )
1454 return pd.Series(
1455 index=index,
1456 data=[
1457 # pylint: disable=g-complex-comprehension
1458 IntVar(
1459 model=self.__model,
1460 name=f"{name}[{i}]",
1461 domain=sorted_interval_list.Domain(
1462 lower_bounds[i], upper_bounds[i]
1463 ),
1464 )
1465 for i in index
1466 ],
1467 )
1468
1470 self,
1471 name: str,
1472 index: pd.Index,
1473 ) -> pd.Series:
1474 """Creates a series of (scalar-valued) variables with the given name.
1475
1476 Args:
1477 name (str): Required. The name of the variable set.
1478 index (pd.Index): Required. The index to use for the variable set.
1479
1480 Returns:
1481 pd.Series: The variable set indexed by its corresponding dimensions.
1482
1483 Raises:
1484 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1485 ValueError: if the `name` is not a valid identifier or already exists.
1486 """
1487 return self.new_int_var_series(
1488 name=name, index=index, lower_bounds=0, upper_bounds=1
1489 )
1490
1491 # Linear constraints.
1492
1494 self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT
1495 ) -> Constraint:
1496 """Adds the constraint: `lb <= linear_expr <= ub`."""
1498 linear_expr, sorted_interval_list.Domain(lb, ub)
1499 )
1500
1502 self, linear_expr: LinearExprT, domain: sorted_interval_list.Domain
1503 ) -> Constraint:
1504 """Adds the constraint: `linear_expr` in `domain`."""
1505 if isinstance(linear_expr, LinearExpr):
1506 ct = Constraint(self)
1507 model_ct = self.__model.constraints[ct.index]
1508 coeffs_map, constant = linear_expr.get_integer_var_value_map()
1509 for t in coeffs_map.items():
1510 if not isinstance(t[0], IntVar):
1511 raise TypeError("Wrong argument" + str(t))
1512 c = cmh.assert_is_int64(t[1])
1513 model_ct.linear.vars.append(t[0].index)
1514 model_ct.linear.coeffs.append(c)
1515 model_ct.linear.domain.extend(
1516 [
1517 cmh.capped_subtraction(x, constant)
1518 for x in domain.flattened_intervals()
1519 ]
1520 )
1521 return ct
1522 if isinstance(linear_expr, IntegralTypes):
1523 if not domain.contains(int(linear_expr)):
1524 return self.add_bool_oradd_bool_oradd_bool_or([]) # Evaluate to false.
1525 else:
1526 return self.add_bool_andadd_bool_andadd_bool_and([]) # Evaluate to true.
1527 raise TypeError(
1528 "not supported: CpModel.add_linear_expression_in_domain("
1529 + str(linear_expr)
1530 + " "
1531 + str(domain)
1532 + ")"
1533 )
1534
1535 @overload
1536 def add(self, ct: BoundedLinearExpression) -> Constraint: ...
1537
1538 @overload
1539 def add(self, ct: Union[bool, np.bool_]) -> Constraint: ...
1540
1541 def add(self, ct):
1542 """Adds a `BoundedLinearExpression` to the model.
1543
1544 Args:
1545 ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
1546
1547 Returns:
1548 An instance of the `Constraint` class.
1549 """
1550 if isinstance(ct, BoundedLinearExpression):
1552 ct.expression(),
1553 sorted_interval_list.Domain.from_flat_intervals(ct.bounds()),
1554 )
1555 if ct and cmh.is_boolean(ct):
1556 return self.add_bool_oradd_bool_oradd_bool_or([True])
1557 if not ct and cmh.is_boolean(ct):
1558 return self.add_bool_oradd_bool_oradd_bool_or([]) # Evaluate to false.
1559 raise TypeError("not supported: CpModel.add(" + str(ct) + ")")
1560
1561 # General Integer Constraints.
1562
1563 @overload
1564 def add_all_different(self, expressions: Iterable[LinearExprT]) -> Constraint: ...
1565
1566 @overload
1567 def add_all_different(self, *expressions: LinearExprT) -> Constraint: ...
1568
1569 def add_all_different(self, *expressions):
1570 """Adds AllDifferent(expressions).
1571
1572 This constraint forces all expressions to have different values.
1573
1574 Args:
1575 *expressions: simple expressions of the form a * var + constant.
1576
1577 Returns:
1578 An instance of the `Constraint` class.
1579 """
1580 ct = Constraint(self)
1581 model_ct = self.__model.constraints[ct.index]
1582 expanded = expand_generator_or_tuple(expressions)
1583 model_ct.all_diff.exprs.extend(
1584 self.parse_linear_expression(x) for x in expanded
1585 )
1586 return ct
1587
1589 self, index: VariableT, variables: Sequence[VariableT], target: VariableT
1590 ) -> Constraint:
1591 """Adds the element constraint: `variables[index] == target`.
1592
1593 Args:
1594 index: The index of the variable that's being constrained.
1595 variables: A list of variables.
1596 target: The value that the variable must be equal to.
1597
1598 Returns:
1599 An instance of the `Constraint` class.
1600 """
1601
1602 if not variables:
1603 raise ValueError("add_element expects a non-empty variables array")
1604
1605 if isinstance(index, IntegralTypes):
1606 variable: VariableT = list(variables)[int(index)]
1607 return self.addaddadd(variable == target)
1608
1609 ct = Constraint(self)
1610 model_ct = self.__model.constraints[ct.index]
1611 model_ct.element.index = self.get_or_make_index(index)
1612 model_ct.element.vars.extend([self.get_or_make_index(x) for x in variables])
1613 model_ct.element.target = self.get_or_make_index(target)
1614 return ct
1615
1616 def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
1617 """Adds Circuit(arcs).
1618
1619 Adds a circuit constraint from a sparse list of arcs that encode the graph.
1620
1621 A circuit is a unique Hamiltonian path in a subgraph of the total
1622 graph. In case a node 'i' is not in the path, then there must be a
1623 loop arc 'i -> i' associated with a true literal. Otherwise
1624 this constraint will fail.
1625
1626 Args:
1627 arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
1628 literal). The arc is selected in the circuit if the literal is true.
1629 Both source_node and destination_node must be integers between 0 and the
1630 number of nodes - 1.
1631
1632 Returns:
1633 An instance of the `Constraint` class.
1634
1635 Raises:
1636 ValueError: If the list of arcs is empty.
1637 """
1638 if not arcs:
1639 raise ValueError("add_circuit expects a non-empty array of arcs")
1640 ct = Constraint(self)
1641 model_ct = self.__model.constraints[ct.index]
1642 for arc in arcs:
1643 tail = cmh.assert_is_int32(arc[0])
1644 head = cmh.assert_is_int32(arc[1])
1645 lit = self.get_or_make_boolean_index(arc[2])
1646 model_ct.circuit.tails.append(tail)
1647 model_ct.circuit.heads.append(head)
1648 model_ct.circuit.literals.append(lit)
1649 return ct
1650
1651 def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
1652 """Adds a multiple circuit constraint, aka the 'VRP' constraint.
1653
1654 The direct graph where arc #i (from tails[i] to head[i]) is present iff
1655 literals[i] is true must satisfy this set of properties:
1656 - #incoming arcs == 1 except for node 0.
1657 - #outgoing arcs == 1 except for node 0.
1658 - for node zero, #incoming arcs == #outgoing arcs.
1659 - There are no duplicate arcs.
1660 - Self-arcs are allowed except for node 0.
1661 - There is no cycle in this graph, except through node 0.
1662
1663 Args:
1664 arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
1665 literal). The arc is selected in the circuit if the literal is true.
1666 Both source_node and destination_node must be integers between 0 and the
1667 number of nodes - 1.
1668
1669 Returns:
1670 An instance of the `Constraint` class.
1671
1672 Raises:
1673 ValueError: If the list of arcs is empty.
1674 """
1675 if not arcs:
1676 raise ValueError("add_multiple_circuit expects a non-empty array of arcs")
1677 ct = Constraint(self)
1678 model_ct = self.__model.constraints[ct.index]
1679 for arc in arcs:
1680 tail = cmh.assert_is_int32(arc[0])
1681 head = cmh.assert_is_int32(arc[1])
1682 lit = self.get_or_make_boolean_index(arc[2])
1683 model_ct.routes.tails.append(tail)
1684 model_ct.routes.heads.append(head)
1685 model_ct.routes.literals.append(lit)
1686 return ct
1687
1689 self,
1690 variables: Sequence[VariableT],
1691 tuples_list: Iterable[Sequence[IntegralT]],
1692 ) -> Constraint:
1693 """Adds AllowedAssignments(variables, tuples_list).
1694
1695 An AllowedAssignments constraint is a constraint on an array of variables,
1696 which requires that when all variables are assigned values, the resulting
1697 array equals one of the tuples in `tuple_list`.
1698
1699 Args:
1700 variables: A list of variables.
1701 tuples_list: A list of admissible tuples. Each tuple must have the same
1702 length as the variables, and the ith value of a tuple corresponds to the
1703 ith variable.
1704
1705 Returns:
1706 An instance of the `Constraint` class.
1707
1708 Raises:
1709 TypeError: If a tuple does not have the same size as the list of
1710 variables.
1711 ValueError: If the array of variables is empty.
1712 """
1713
1714 if not variables:
1715 raise ValueError(
1716 "add_allowed_assignments expects a non-empty variables array"
1717 )
1718
1719 ct: Constraint = Constraint(self)
1720 model_ct = self.__model.constraints[ct.index]
1721 model_ct.table.vars.extend([self.get_or_make_index(x) for x in variables])
1722 arity: int = len(variables)
1723 for t in tuples_list:
1724 if len(t) != arity:
1725 raise TypeError("Tuple " + str(t) + " has the wrong arity")
1726
1727 # duck-typing (no explicit type checks here)
1728 try:
1729 model_ct.table.values.extend(a for b in tuples_list for a in b)
1730 except ValueError as ex:
1731 raise TypeError(f"add_xxx_assignment: Not an integer or does not fit in an int64_t: {ex.args}") from ex
1732
1733 return ct
1734
1736 self,
1737 variables: Sequence[VariableT],
1738 tuples_list: Iterable[Sequence[IntegralT]],
1739 ) -> Constraint:
1740 """Adds add_forbidden_assignments(variables, [tuples_list]).
1741
1742 A ForbiddenAssignments constraint is a constraint on an array of variables
1743 where the list of impossible combinations is provided in the tuples list.
1744
1745 Args:
1746 variables: A list of variables.
1747 tuples_list: A list of forbidden tuples. Each tuple must have the same
1748 length as the variables, and the *i*th value of a tuple corresponds to
1749 the *i*th variable.
1750
1751 Returns:
1752 An instance of the `Constraint` class.
1753
1754 Raises:
1755 TypeError: If a tuple does not have the same size as the list of
1756 variables.
1757 ValueError: If the array of variables is empty.
1758 """
1759
1760 if not variables:
1761 raise ValueError(
1762 "add_forbidden_assignments expects a non-empty variables array"
1763 )
1764
1765 index = len(self.__model.constraints)
1766 ct: Constraint = self.add_allowed_assignments(variables, tuples_list)
1767 self.__model.constraints[index].table.negated = True
1768 return ct
1769
1771 self,
1772 transition_variables: Sequence[VariableT],
1773 starting_state: IntegralT,
1774 final_states: Sequence[IntegralT],
1775 transition_triples: Sequence[Tuple[IntegralT, IntegralT, IntegralT]],
1776 ) -> Constraint:
1777 """Adds an automaton constraint.
1778
1779 An automaton constraint takes a list of variables (of size *n*), an initial
1780 state, a set of final states, and a set of transitions. A transition is a
1781 triplet (*tail*, *transition*, *head*), where *tail* and *head* are states,
1782 and *transition* is the label of an arc from *head* to *tail*,
1783 corresponding to the value of one variable in the list of variables.
1784
1785 This automaton will be unrolled into a flow with *n* + 1 phases. Each phase
1786 contains the possible states of the automaton. The first state contains the
1787 initial state. The last phase contains the final states.
1788
1789 Between two consecutive phases *i* and *i* + 1, the automaton creates a set
1790 of arcs. For each transition (*tail*, *transition*, *head*), it will add
1791 an arc from the state *tail* of phase *i* and the state *head* of phase
1792 *i* + 1. This arc is labeled by the value *transition* of the variables
1793 `variables[i]`. That is, this arc can only be selected if `variables[i]`
1794 is assigned the value *transition*.
1795
1796 A feasible solution of this constraint is an assignment of variables such
1797 that, starting from the initial state in phase 0, there is a path labeled by
1798 the values of the variables that ends in one of the final states in the
1799 final phase.
1800
1801 Args:
1802 transition_variables: A non-empty list of variables whose values
1803 correspond to the labels of the arcs traversed by the automaton.
1804 starting_state: The initial state of the automaton.
1805 final_states: A non-empty list of admissible final states.
1806 transition_triples: A list of transitions for the automaton, in the
1807 following format (current_state, variable_value, next_state).
1808
1809 Returns:
1810 An instance of the `Constraint` class.
1811
1812 Raises:
1813 ValueError: if `transition_variables`, `final_states`, or
1814 `transition_triples` are empty.
1815 """
1816
1817 if not transition_variables:
1818 raise ValueError(
1819 "add_automaton expects a non-empty transition_variables array"
1820 )
1821 if not final_states:
1822 raise ValueError("add_automaton expects some final states")
1823
1824 if not transition_triples:
1825 raise ValueError("add_automaton expects some transition triples")
1826
1827 ct = Constraint(self)
1828 model_ct = self.__model.constraints[ct.index]
1829 model_ct.automaton.vars.extend(
1830 [self.get_or_make_index(x) for x in transition_variables]
1831 )
1832 starting_state = cmh.assert_is_int64(starting_state)
1833 model_ct.automaton.starting_state = starting_state
1834 for v in final_states:
1835 v = cmh.assert_is_int64(v)
1836 model_ct.automaton.final_states.append(v)
1837 for t in transition_triples:
1838 if len(t) != 3:
1839 raise TypeError("Tuple " + str(t) + " has the wrong arity (!= 3)")
1840 tail = cmh.assert_is_int64(t[0])
1841 label = cmh.assert_is_int64(t[1])
1842 head = cmh.assert_is_int64(t[2])
1843 model_ct.automaton.transition_tail.append(tail)
1844 model_ct.automaton.transition_label.append(label)
1845 model_ct.automaton.transition_head.append(head)
1846 return ct
1847
1849 self,
1850 variables: Sequence[VariableT],
1851 inverse_variables: Sequence[VariableT],
1852 ) -> Constraint:
1853 """Adds Inverse(variables, inverse_variables).
1854
1855 An inverse constraint enforces that if `variables[i]` is assigned a value
1856 `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa.
1857
1858 Args:
1859 variables: An array of integer variables.
1860 inverse_variables: An array of integer variables.
1861
1862 Returns:
1863 An instance of the `Constraint` class.
1864
1865 Raises:
1866 TypeError: if variables and inverse_variables have different lengths, or
1867 if they are empty.
1868 """
1869
1870 if not variables or not inverse_variables:
1871 raise TypeError("The Inverse constraint does not accept empty arrays")
1872 if len(variables) != len(inverse_variables):
1873 raise TypeError(
1874 "In the inverse constraint, the two array variables and"
1875 " inverse_variables must have the same length."
1876 )
1877 ct = Constraint(self)
1878 model_ct = self.__model.constraints[ct.index]
1879 model_ct.inverse.f_direct.extend([self.get_or_make_index(x) for x in variables])
1880 model_ct.inverse.f_inverse.extend(
1881 [self.get_or_make_index(x) for x in inverse_variables]
1882 )
1883 return ct
1884
1886 self,
1887 times: Iterable[LinearExprT],
1888 level_changes: Iterable[LinearExprT],
1889 min_level: int,
1890 max_level: int,
1891 ) -> Constraint:
1892 """Adds Reservoir(times, level_changes, min_level, max_level).
1893
1894 Maintains a reservoir level within bounds. The water level starts at 0, and
1895 at any time, it must be between min_level and max_level.
1896
1897 If the affine expression `times[i]` is assigned a value t, then the current
1898 level changes by `level_changes[i]`, which is constant, at time t.
1899
1900 Note that min level must be <= 0, and the max level must be >= 0. Please
1901 use fixed level_changes to simulate initial state.
1902
1903 Therefore, at any time:
1904 sum(level_changes[i] if times[i] <= t) in [min_level, max_level]
1905
1906 Args:
1907 times: A list of 1-var affine expressions (a * x + b) which specify the
1908 time of the filling or emptying the reservoir.
1909 level_changes: A list of integer values that specifies the amount of the
1910 emptying or filling. Currently, variable demands are not supported.
1911 min_level: At any time, the level of the reservoir must be greater or
1912 equal than the min level.
1913 max_level: At any time, the level of the reservoir must be less or equal
1914 than the max level.
1915
1916 Returns:
1917 An instance of the `Constraint` class.
1918
1919 Raises:
1920 ValueError: if max_level < min_level.
1921
1922 ValueError: if max_level < 0.
1923
1924 ValueError: if min_level > 0
1925 """
1926
1927 if max_level < min_level:
1928 raise ValueError("Reservoir constraint must have a max_level >= min_level")
1929
1930 if max_level < 0:
1931 raise ValueError("Reservoir constraint must have a max_level >= 0")
1932
1933 if min_level > 0:
1934 raise ValueError("Reservoir constraint must have a min_level <= 0")
1935
1936 ct = Constraint(self)
1937 model_ct = self.__model.constraints[ct.index]
1938 model_ct.reservoir.time_exprs.extend(
1939 [self.parse_linear_expression(x) for x in times]
1940 )
1941 model_ct.reservoir.level_changes.extend(
1942 [self.parse_linear_expression(x) for x in level_changes]
1943 )
1944 model_ct.reservoir.min_level = min_level
1945 model_ct.reservoir.max_level = max_level
1946 return ct
1947
1949 self,
1950 times: Iterable[LinearExprT],
1951 level_changes: Iterable[LinearExprT],
1952 actives: Iterable[LiteralT],
1953 min_level: int,
1954 max_level: int,
1955 ) -> Constraint:
1956 """Adds Reservoir(times, level_changes, actives, min_level, max_level).
1957
1958 Maintains a reservoir level within bounds. The water level starts at 0, and
1959 at any time, it must be between min_level and max_level.
1960
1961 If the variable `times[i]` is assigned a value t, and `actives[i]` is
1962 `True`, then the current level changes by `level_changes[i]`, which is
1963 constant,
1964 at time t.
1965
1966 Note that min level must be <= 0, and the max level must be >= 0. Please
1967 use fixed level_changes to simulate initial state.
1968
1969 Therefore, at any time:
1970 sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level,
1971 max_level]
1972
1973
1974 The array of boolean variables 'actives', if defined, indicates which
1975 actions are actually performed.
1976
1977 Args:
1978 times: A list of 1-var affine expressions (a * x + b) which specify the
1979 time of the filling or emptying the reservoir.
1980 level_changes: A list of integer values that specifies the amount of the
1981 emptying or filling. Currently, variable demands are not supported.
1982 actives: a list of boolean variables. They indicates if the
1983 emptying/refilling events actually take place.
1984 min_level: At any time, the level of the reservoir must be greater or
1985 equal than the min level.
1986 max_level: At any time, the level of the reservoir must be less or equal
1987 than the max level.
1988
1989 Returns:
1990 An instance of the `Constraint` class.
1991
1992 Raises:
1993 ValueError: if max_level < min_level.
1994
1995 ValueError: if max_level < 0.
1996
1997 ValueError: if min_level > 0
1998 """
1999
2000 if max_level < min_level:
2001 raise ValueError("Reservoir constraint must have a max_level >= min_level")
2002
2003 if max_level < 0:
2004 raise ValueError("Reservoir constraint must have a max_level >= 0")
2005
2006 if min_level > 0:
2007 raise ValueError("Reservoir constraint must have a min_level <= 0")
2008
2009 ct = Constraint(self)
2010 model_ct = self.__model.constraints[ct.index]
2011 model_ct.reservoir.time_exprs.extend(
2012 [self.parse_linear_expression(x) for x in times]
2013 )
2014 model_ct.reservoir.level_changes.extend(
2015 [self.parse_linear_expression(x) for x in level_changes]
2016 )
2017 model_ct.reservoir.active_literals.extend(
2018 [self.get_or_make_boolean_index(x) for x in actives]
2019 )
2020 model_ct.reservoir.min_level = min_level
2021 model_ct.reservoir.max_level = max_level
2022 return ct
2023
2025 self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0
2026 ):
2027 """Adds `var == i + offset <=> bool_var_array[i] == true for all i`."""
2028
2029 for i, bool_var in enumerate(bool_var_array):
2030 b_index = bool_var.index
2031 var_index = var.index
2032 model_ct = self.__model.constraints.add()
2033 model_ct.linear.vars.append(var_index)
2034 model_ct.linear.coeffs.append(1)
2035 offset_as_int = int(offset)
2036 model_ct.linear.domain.extend([offset_as_int + i, offset_as_int + i])
2037 model_ct.enforcement_literal.append(b_index)
2038
2039 model_ct = self.__model.constraints.add()
2040 model_ct.linear.vars.append(var_index)
2041 model_ct.linear.coeffs.append(1)
2042 model_ct.enforcement_literal.append(-b_index - 1)
2043 if offset + i - 1 >= INT_MIN:
2044 model_ct.linear.domain.extend([INT_MIN, offset_as_int + i - 1])
2045 if offset + i + 1 <= INT_MAX:
2046 model_ct.linear.domain.extend([offset_as_int + i + 1, INT_MAX])
2047
2048 def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint:
2049 """Adds `a => b` (`a` implies `b`)."""
2050 ct = Constraint(self)
2051 model_ct = self.__model.constraints[ct.index]
2052 model_ct.bool_or.literals.append(self.get_or_make_boolean_index(b))
2053 model_ct.enforcement_literal.append(self.get_or_make_boolean_index(a))
2054 return ct
2055
2056 @overload
2057 def add_bool_or(self, literals: Iterable[LiteralT]) -> Constraint: ...
2058
2059 @overload
2060 def add_bool_or(self, *literals: LiteralT) -> Constraint: ...
2061
2062 def add_bool_or(self, *literals):
2063 """Adds `Or(literals) == true`: sum(literals) >= 1."""
2064 ct = Constraint(self)
2065 model_ct = self.__model.constraints[ct.index]
2066 model_ct.bool_or.literals.extend(
2067 [
2069 for x in expand_generator_or_tuple(literals)
2070 ]
2071 )
2072 return ct
2073
2074 @overload
2075 def add_at_least_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
2076
2077 @overload
2078 def add_at_least_one(self, *literals: LiteralT) -> Constraint: ...
2079
2080 def add_at_least_one(self, *literals):
2081 """Same as `add_bool_or`: `sum(literals) >= 1`."""
2082 return self.add_bool_oradd_bool_oradd_bool_or(*literals)
2083
2084 @overload
2085 def add_at_most_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
2086
2087 @overload
2088 def add_at_most_one(self, *literals: LiteralT) -> Constraint: ...
2089
2090 def add_at_most_one(self, *literals):
2091 """Adds `AtMostOne(literals)`: `sum(literals) <= 1`."""
2092 ct = Constraint(self)
2093 model_ct = self.__model.constraints[ct.index]
2094 model_ct.at_most_one.literals.extend(
2095 [
2097 for x in expand_generator_or_tuple(literals)
2098 ]
2099 )
2100 return ct
2101
2102 @overload
2103 def add_exactly_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
2104
2105 @overload
2106 def add_exactly_one(self, *literals: LiteralT) -> Constraint: ...
2107
2108 def add_exactly_one(self, *literals):
2109 """Adds `ExactlyOne(literals)`: `sum(literals) == 1`."""
2110 ct = Constraint(self)
2111 model_ct = self.__model.constraints[ct.index]
2112 model_ct.exactly_one.literals.extend(
2113 [
2115 for x in expand_generator_or_tuple(literals)
2116 ]
2117 )
2118 return ct
2119
2120 @overload
2121 def add_bool_and(self, literals: Iterable[LiteralT]) -> Constraint: ...
2122
2123 @overload
2124 def add_bool_and(self, *literals: LiteralT) -> Constraint: ...
2125
2126 def add_bool_and(self, *literals):
2127 """Adds `And(literals) == true`."""
2128 ct = Constraint(self)
2129 model_ct = self.__model.constraints[ct.index]
2130 model_ct.bool_and.literals.extend(
2131 [
2133 for x in expand_generator_or_tuple(literals)
2134 ]
2135 )
2136 return ct
2137
2138 @overload
2139 def add_bool_xor(self, literals: Iterable[LiteralT]) -> Constraint: ...
2140
2141 @overload
2142 def add_bool_xor(self, *literals: LiteralT) -> Constraint: ...
2143
2144 def add_bool_xor(self, *literals):
2145 """Adds `XOr(literals) == true`.
2146
2147 In contrast to add_bool_or and add_bool_and, it does not support
2148 .only_enforce_if().
2149
2150 Args:
2151 *literals: the list of literals in the constraint.
2152
2153 Returns:
2154 An `Constraint` object.
2155 """
2156 ct = Constraint(self)
2157 model_ct = self.__model.constraints[ct.index]
2158 model_ct.bool_xor.literals.extend(
2159 [
2161 for x in expand_generator_or_tuple(literals)
2162 ]
2163 )
2164 return ct
2165
2167 self, target: LinearExprT, exprs: Iterable[LinearExprT]
2168 ) -> Constraint:
2169 """Adds `target == Min(exprs)`."""
2170 ct = Constraint(self)
2171 model_ct = self.__model.constraints[ct.index]
2172 model_ct.lin_max.exprs.extend(
2173 [self.parse_linear_expression(x, True) for x in exprs]
2174 )
2175 model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target, True))
2176 return ct
2177
2179 self, target: LinearExprT, exprs: Iterable[LinearExprT]
2180 ) -> Constraint:
2181 """Adds `target == Max(exprs)`."""
2182 ct = Constraint(self)
2183 model_ct = self.__model.constraints[ct.index]
2184 model_ct.lin_max.exprs.extend([self.parse_linear_expression(x) for x in exprs])
2185 model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
2186 return ct
2187
2189 self, target: LinearExprT, num: LinearExprT, denom: LinearExprT
2190 ) -> Constraint:
2191 """Adds `target == num // denom` (integer division rounded towards 0)."""
2192 ct = Constraint(self)
2193 model_ct = self.__model.constraints[ct.index]
2194 model_ct.int_div.exprs.append(self.parse_linear_expression(num))
2195 model_ct.int_div.exprs.append(self.parse_linear_expression(denom))
2196 model_ct.int_div.target.CopyFrom(self.parse_linear_expression(target))
2197 return ct
2198
2199 def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint:
2200 """Adds `target == Abs(expr)`."""
2201 ct = Constraint(self)
2202 model_ct = self.__model.constraints[ct.index]
2203 model_ct.lin_max.exprs.append(self.parse_linear_expression(expr))
2204 model_ct.lin_max.exprs.append(self.parse_linear_expression(expr, True))
2205 model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
2206 return ct
2207
2209 self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT
2210 ) -> Constraint:
2211 """Adds `target = expr % mod`.
2212
2213 It uses the C convention, that is the result is the remainder of the
2214 integral division rounded towards 0.
2215
2216 For example:
2217 * 10 % 3 = 1
2218 * -10 % 3 = -1
2219 * 10 % -3 = 1
2220 * -10 % -3 = -1
2221
2222 Args:
2223 target: the target expression.
2224 expr: the expression to compute the modulo of.
2225 mod: the modulus expression.
2226
2227 Returns:
2228 A `Constraint` object.
2229 """
2230 ct = Constraint(self)
2231 model_ct = self.__model.constraints[ct.index]
2232 model_ct.int_mod.exprs.append(self.parse_linear_expression(expr))
2233 model_ct.int_mod.exprs.append(self.parse_linear_expression(mod))
2234 model_ct.int_mod.target.CopyFrom(self.parse_linear_expression(target))
2235 return ct
2236
2238 self,
2239 target: LinearExprT,
2240 *expressions: Union[Iterable[LinearExprT], LinearExprT],
2241 ) -> Constraint:
2242 """Adds `target == expressions[0] * .. * expressions[n]`."""
2243 ct = Constraint(self)
2244 model_ct = self.__model.constraints[ct.index]
2245 model_ct.int_prod.exprs.extend(
2246 [
2247 self.parse_linear_expression(expr)
2248 for expr in expand_generator_or_tuple(expressions)
2249 ]
2250 )
2251 model_ct.int_prod.target.CopyFrom(self.parse_linear_expression(target))
2252 return ct
2253
2254 # Scheduling support
2255
2257 self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str
2258 ) -> IntervalVar:
2259 """Creates an interval variable from start, size, and end.
2260
2261 An interval variable is a constraint, that is itself used in other
2262 constraints like NoOverlap.
2263
2264 Internally, it ensures that `start + size == end`.
2265
2266 Args:
2267 start: The start of the interval. It must be of the form a * var + b.
2268 size: The size of the interval. It must be of the form a * var + b.
2269 end: The end of the interval. It must be of the form a * var + b.
2270 name: The name of the interval variable.
2271
2272 Returns:
2273 An `IntervalVar` object.
2274 """
2275
2276 start_expr = self.parse_linear_expression(start)
2277 size_expr = self.parse_linear_expression(size)
2278 end_expr = self.parse_linear_expression(end)
2279 if len(start_expr.vars) > 1:
2280 raise TypeError(
2281 "cp_model.new_interval_var: start must be 1-var affine or constant."
2282 )
2283 if len(size_expr.vars) > 1:
2284 raise TypeError(
2285 "cp_model.new_interval_var: size must be 1-var affine or constant."
2286 )
2287 if len(end_expr.vars) > 1:
2288 raise TypeError(
2289 "cp_model.new_interval_var: end must be 1-var affine or constant."
2290 )
2291 return IntervalVar(self.__model, start_expr, size_expr, end_expr, None, name)
2292
2294 self,
2295 name: str,
2296 index: pd.Index,
2297 starts: Union[LinearExprT, pd.Series],
2298 sizes: Union[LinearExprT, pd.Series],
2299 ends: Union[LinearExprT, pd.Series],
2300 ) -> pd.Series:
2301 """Creates a series of interval variables with the given name.
2302
2303 Args:
2304 name (str): Required. The name of the variable set.
2305 index (pd.Index): Required. The index to use for the variable set.
2306 starts (Union[LinearExprT, pd.Series]): The start of each interval in the
2307 set. If a `pd.Series` is passed in, it will be based on the
2308 corresponding values of the pd.Series.
2309 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
2310 set. If a `pd.Series` is passed in, it will be based on the
2311 corresponding values of the pd.Series.
2312 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
2313 set. If a `pd.Series` is passed in, it will be based on the
2314 corresponding values of the pd.Series.
2315
2316 Returns:
2317 pd.Series: The interval variable set indexed by its corresponding
2318 dimensions.
2319
2320 Raises:
2321 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
2322 ValueError: if the `name` is not a valid identifier or already exists.
2323 ValueError: if the all the indexes do not match.
2324 """
2325 if not isinstance(index, pd.Index):
2326 raise TypeError("Non-index object is used as index")
2327 if not name.isidentifier():
2328 raise ValueError("name={} is not a valid identifier".format(name))
2329
2333 interval_array = []
2334 for i in index:
2335 interval_array.append(
2336 self.new_interval_var(
2337 start=starts[i],
2338 size=sizes[i],
2339 end=ends[i],
2340 name=f"{name}[{i}]",
2341 )
2342 )
2343 return pd.Series(index=index, data=interval_array)
2344
2346 self, start: LinearExprT, size: IntegralT, name: str
2347 ) -> IntervalVar:
2348 """Creates an interval variable from start, and a fixed size.
2349
2350 An interval variable is a constraint, that is itself used in other
2351 constraints like NoOverlap.
2352
2353 Args:
2354 start: The start of the interval. It must be of the form a * var + b.
2355 size: The size of the interval. It must be an integer value.
2356 name: The name of the interval variable.
2357
2358 Returns:
2359 An `IntervalVar` object.
2360 """
2361 size = cmh.assert_is_int64(size)
2362 start_expr = self.parse_linear_expression(start)
2363 size_expr = self.parse_linear_expression(size)
2364 end_expr = self.parse_linear_expression(start + size)
2365 if len(start_expr.vars) > 1:
2366 raise TypeError(
2367 "cp_model.new_interval_var: start must be affine or constant."
2368 )
2369 return IntervalVar(self.__model, start_expr, size_expr, end_expr, None, name)
2370
2372 self,
2373 name: str,
2374 index: pd.Index,
2375 starts: Union[LinearExprT, pd.Series],
2376 sizes: Union[IntegralT, pd.Series],
2377 ) -> pd.Series:
2378 """Creates a series of interval variables with the given name.
2379
2380 Args:
2381 name (str): Required. The name of the variable set.
2382 index (pd.Index): Required. The index to use for the variable set.
2383 starts (Union[LinearExprT, pd.Series]): The start of each interval in the
2384 set. If a `pd.Series` is passed in, it will be based on the
2385 corresponding values of the pd.Series.
2386 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
2387 the set. If a `pd.Series` is passed in, it will be based on the
2388 corresponding values of the pd.Series.
2389
2390 Returns:
2391 pd.Series: The interval variable set indexed by its corresponding
2392 dimensions.
2393
2394 Raises:
2395 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
2396 ValueError: if the `name` is not a valid identifier or already exists.
2397 ValueError: if the all the indexes do not match.
2398 """
2399 if not isinstance(index, pd.Index):
2400 raise TypeError("Non-index object is used as index")
2401 if not name.isidentifier():
2402 raise ValueError("name={} is not a valid identifier".format(name))
2403
2406 interval_array = []
2407 for i in index:
2408 interval_array.append(
2410 start=starts[i],
2411 size=sizes[i],
2412 name=f"{name}[{i}]",
2413 )
2414 )
2415 return pd.Series(index=index, data=interval_array)
2416
2418 self,
2419 start: LinearExprT,
2420 size: LinearExprT,
2421 end: LinearExprT,
2422 is_present: LiteralT,
2423 name: str,
2424 ) -> IntervalVar:
2425 """Creates an optional interval var from start, size, end, and is_present.
2426
2427 An optional interval variable is a constraint, that is itself used in other
2428 constraints like NoOverlap. This constraint is protected by a presence
2429 literal that indicates if it is active or not.
2430
2431 Internally, it ensures that `is_present` implies `start + size ==
2432 end`.
2433
2434 Args:
2435 start: The start of the interval. It must be of the form a * var + b.
2436 size: The size of the interval. It must be of the form a * var + b.
2437 end: The end of the interval. It must be of the form a * var + b.
2438 is_present: A literal that indicates if the interval is active or not. A
2439 inactive interval is simply ignored by all constraints.
2440 name: The name of the interval variable.
2441
2442 Returns:
2443 An `IntervalVar` object.
2444 """
2445
2446 # Creates the IntervalConstraintProto object.
2447 is_present_index = self.get_or_make_boolean_index(is_present)
2448 start_expr = self.parse_linear_expression(start)
2449 size_expr = self.parse_linear_expression(size)
2450 end_expr = self.parse_linear_expression(end)
2451 if len(start_expr.vars) > 1:
2452 raise TypeError(
2453 "cp_model.new_interval_var: start must be affine or constant."
2454 )
2455 if len(size_expr.vars) > 1:
2456 raise TypeError(
2457 "cp_model.new_interval_var: size must be affine or constant."
2458 )
2459 if len(end_expr.vars) > 1:
2460 raise TypeError(
2461 "cp_model.new_interval_var: end must be affine or constant."
2462 )
2463 return IntervalVar(
2464 self.__model, start_expr, size_expr, end_expr, is_present_index, name
2465 )
2466
2468 self,
2469 name: str,
2470 index: pd.Index,
2471 starts: Union[LinearExprT, pd.Series],
2472 sizes: Union[LinearExprT, pd.Series],
2473 ends: Union[LinearExprT, pd.Series],
2474 are_present: Union[LiteralT, pd.Series],
2475 ) -> pd.Series:
2476 """Creates a series of interval variables with the given name.
2477
2478 Args:
2479 name (str): Required. The name of the variable set.
2480 index (pd.Index): Required. The index to use for the variable set.
2481 starts (Union[LinearExprT, pd.Series]): The start of each interval in the
2482 set. If a `pd.Series` is passed in, it will be based on the
2483 corresponding values of the pd.Series.
2484 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
2485 set. If a `pd.Series` is passed in, it will be based on the
2486 corresponding values of the pd.Series.
2487 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
2488 set. If a `pd.Series` is passed in, it will be based on the
2489 corresponding values of the pd.Series.
2490 are_present (Union[LiteralT, pd.Series]): The performed literal of each
2491 interval in the set. If a `pd.Series` is passed in, it will be based on
2492 the corresponding values of the pd.Series.
2493
2494 Returns:
2495 pd.Series: The interval variable set indexed by its corresponding
2496 dimensions.
2497
2498 Raises:
2499 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
2500 ValueError: if the `name` is not a valid identifier or already exists.
2501 ValueError: if the all the indexes do not match.
2502 """
2503 if not isinstance(index, pd.Index):
2504 raise TypeError("Non-index object is used as index")
2505 if not name.isidentifier():
2506 raise ValueError("name={} is not a valid identifier".format(name))
2507
2511 are_present = _convert_to_literal_series_and_validate_index(are_present, index)
2512
2513 interval_array = []
2514 for i in index:
2515 interval_array.append(
2517 start=starts[i],
2518 size=sizes[i],
2519 end=ends[i],
2520 is_present=are_present[i],
2521 name=f"{name}[{i}]",
2522 )
2523 )
2524 return pd.Series(index=index, data=interval_array)
2525
2527 self,
2528 start: LinearExprT,
2529 size: IntegralT,
2530 is_present: LiteralT,
2531 name: str,
2532 ) -> IntervalVar:
2533 """Creates an interval variable from start, and a fixed size.
2534
2535 An interval variable is a constraint, that is itself used in other
2536 constraints like NoOverlap.
2537
2538 Args:
2539 start: The start of the interval. It must be of the form a * var + b.
2540 size: The size of the interval. It must be an integer value.
2541 is_present: A literal that indicates if the interval is active or not. A
2542 inactive interval is simply ignored by all constraints.
2543 name: The name of the interval variable.
2544
2545 Returns:
2546 An `IntervalVar` object.
2547 """
2548 size = cmh.assert_is_int64(size)
2549 start_expr = self.parse_linear_expression(start)
2550 size_expr = self.parse_linear_expression(size)
2551 end_expr = self.parse_linear_expression(start + size)
2552 if len(start_expr.vars) > 1:
2553 raise TypeError(
2554 "cp_model.new_interval_var: start must be affine or constant."
2555 )
2556 is_present_index = self.get_or_make_boolean_index(is_present)
2557 return IntervalVar(
2558 self.__model,
2559 start_expr,
2560 size_expr,
2561 end_expr,
2562 is_present_index,
2563 name,
2564 )
2565
2567 self,
2568 name: str,
2569 index: pd.Index,
2570 starts: Union[LinearExprT, pd.Series],
2571 sizes: Union[IntegralT, pd.Series],
2572 are_present: Union[LiteralT, pd.Series],
2573 ) -> pd.Series:
2574 """Creates a series of interval variables with the given name.
2575
2576 Args:
2577 name (str): Required. The name of the variable set.
2578 index (pd.Index): Required. The index to use for the variable set.
2579 starts (Union[LinearExprT, pd.Series]): The start of each interval in the
2580 set. If a `pd.Series` is passed in, it will be based on the
2581 corresponding values of the pd.Series.
2582 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
2583 the set. If a `pd.Series` is passed in, it will be based on the
2584 corresponding values of the pd.Series.
2585 are_present (Union[LiteralT, pd.Series]): The performed literal of each
2586 interval in the set. If a `pd.Series` is passed in, it will be based on
2587 the corresponding values of the pd.Series.
2588
2589 Returns:
2590 pd.Series: The interval variable set indexed by its corresponding
2591 dimensions.
2592
2593 Raises:
2594 TypeError: if the `index` is invalid (e.g. a `DataFrame`).
2595 ValueError: if the `name` is not a valid identifier or already exists.
2596 ValueError: if the all the indexes do not match.
2597 """
2598 if not isinstance(index, pd.Index):
2599 raise TypeError("Non-index object is used as index")
2600 if not name.isidentifier():
2601 raise ValueError("name={} is not a valid identifier".format(name))
2602
2605 are_present = _convert_to_literal_series_and_validate_index(are_present, index)
2606 interval_array = []
2607 for i in index:
2608 interval_array.append(
2610 start=starts[i],
2611 size=sizes[i],
2612 is_present=are_present[i],
2613 name=f"{name}[{i}]",
2614 )
2615 )
2616 return pd.Series(index=index, data=interval_array)
2617
2618 def add_no_overlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint:
2619 """Adds NoOverlap(interval_vars).
2620
2621 A NoOverlap constraint ensures that all present intervals do not overlap
2622 in time.
2623
2624 Args:
2625 interval_vars: The list of interval variables to constrain.
2626
2627 Returns:
2628 An instance of the `Constraint` class.
2629 """
2630 ct = Constraint(self)
2631 model_ct = self.__model.constraints[ct.index]
2632 model_ct.no_overlap.intervals.extend(
2633 [self.get_interval_index(x) for x in interval_vars]
2634 )
2635 return ct
2636
2638 self,
2639 x_intervals: Iterable[IntervalVar],
2640 y_intervals: Iterable[IntervalVar],
2641 ) -> Constraint:
2642 """Adds NoOverlap2D(x_intervals, y_intervals).
2643
2644 A NoOverlap2D constraint ensures that all present rectangles do not overlap
2645 on a plane. Each rectangle is aligned with the X and Y axis, and is defined
2646 by two intervals which represent its projection onto the X and Y axis.
2647
2648 Furthermore, one box is optional if at least one of the x or y interval is
2649 optional.
2650
2651 Args:
2652 x_intervals: The X coordinates of the rectangles.
2653 y_intervals: The Y coordinates of the rectangles.
2654
2655 Returns:
2656 An instance of the `Constraint` class.
2657 """
2658 ct = Constraint(self)
2659 model_ct = self.__model.constraints[ct.index]
2660 model_ct.no_overlap_2d.x_intervals.extend(
2661 [self.get_interval_index(x) for x in x_intervals]
2662 )
2663 model_ct.no_overlap_2d.y_intervals.extend(
2664 [self.get_interval_index(x) for x in y_intervals]
2665 )
2666 return ct
2667
2669 self,
2670 intervals: Iterable[IntervalVar],
2671 demands: Iterable[LinearExprT],
2672 capacity: LinearExprT,
2673 ) -> Constraint:
2674 """Adds Cumulative(intervals, demands, capacity).
2675
2676 This constraint enforces that:
2677
2678 for all t:
2679 sum(demands[i]
2680 if (start(intervals[i]) <= t < end(intervals[i])) and
2681 (intervals[i] is present)) <= capacity
2682
2683 Args:
2684 intervals: The list of intervals.
2685 demands: The list of demands for each interval. Each demand must be >= 0.
2686 Each demand can be a 1-var affine expression (a * x + b).
2687 capacity: The maximum capacity of the cumulative constraint. It can be a
2688 1-var affine expression (a * x + b).
2689
2690 Returns:
2691 An instance of the `Constraint` class.
2692 """
2693 cumulative = Constraint(self)
2694 model_ct = self.__model.constraints[cumulative.index]
2695 model_ct.cumulative.intervals.extend(
2696 [self.get_interval_index(x) for x in intervals]
2697 )
2698 for d in demands:
2699 model_ct.cumulative.demands.append(self.parse_linear_expression(d))
2700 model_ct.cumulative.capacity.CopyFrom(self.parse_linear_expression(capacity))
2701 return cumulative
2702
2703 # Support for model cloning.
2704 def clone(self) -> "CpModel":
2705 """Reset the model, and creates a new one from a CpModelProto instance."""
2706 clone = CpModel()
2707 clone.proto.CopyFrom(self.protoproto)
2708 clone.rebuild_constant_map()
2709 return clone
2710
2712 """Internal method used during model cloning."""
2713 for i, var in enumerate(self.__model.variables):
2714 if len(var.domain) == 2 and var.domain[0] == var.domain[1]:
2715 self.__constant_map[var.domain[0]] = i
2716
2717 def get_bool_var_from_proto_index(self, index: int) -> IntVar:
2718 """Returns an already created Boolean variable from its index."""
2719 if index < 0 or index >= len(self.__model.variables):
2720 raise ValueError(
2721 f"get_bool_var_from_proto_index: out of bound index {index}"
2722 )
2723 var = self.__model.variables[index]
2724 if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1:
2725 raise ValueError(
2726 f"get_bool_var_from_proto_index: index {index} does not reference"
2727 + " a Boolean variable"
2728 )
2729
2730 return IntVar(self.__model, index, None)
2731
2732 def get_int_var_from_proto_index(self, index: int) -> IntVar:
2733 """Returns an already created integer variable from its index."""
2734 if index < 0 or index >= len(self.__model.variables):
2735 raise ValueError(
2736 f"get_int_var_from_proto_index: out of bound index {index}"
2737 )
2738 return IntVar(self.__model, index, None)
2739
2740 def get_interval_var_from_proto_index(self, index: int) -> IntervalVar:
2741 """Returns an already created interval variable from its index."""
2742 if index < 0 or index >= len(self.__model.constraints):
2743 raise ValueError(
2744 f"get_interval_var_from_proto_index: out of bound index {index}"
2745 )
2746 ct = self.__model.constraints[index]
2747 if not ct.HasField("interval"):
2748 raise ValueError(
2749 f"get_interval_var_from_proto_index: index {index} does not"
2750 " reference an" + " interval variable"
2751 )
2752
2753 return IntervalVar(self.__model, index, None, None, None, None)
2754
2755 # Helpers.
2756
2757 def __str__(self) -> str:
2758 return str(self.__model)
2759
2760 @property
2761 def proto(self) -> cp_model_pb2.CpModelProto:
2762 """Returns the underlying CpModelProto."""
2763 return self.__model
2764
2765 def negated(self, index: int) -> int:
2766 return -index - 1
2767
2768 def get_or_make_index(self, arg: VariableT) -> int:
2769 """Returns the index of a variable, its negation, or a number."""
2770 if isinstance(arg, IntVar):
2771 return arg.index
2772 if (
2773 isinstance(arg, _ProductCst)
2774 and isinstance(arg.expression(), IntVar)
2775 and arg.coefficient() == -1
2776 ):
2777 return -arg.expression().index - 1
2778 if isinstance(arg, IntegralTypes):
2779 arg = cmh.assert_is_int64(arg)
2780 return self.get_or_make_index_from_constant(arg)
2781 raise TypeError("NotSupported: model.get_or_make_index(" + str(arg) + ")")
2782
2783 def get_or_make_boolean_index(self, arg: LiteralT) -> int:
2784 """Returns an index from a boolean expression."""
2785 if isinstance(arg, IntVar):
2787 return arg.index
2788 if isinstance(arg, _NotBooleanVariable):
2789 self.assert_is_boolean_variable(arg.negated())
2790 return arg.index
2791 if isinstance(arg, IntegralTypes):
2792 if arg == ~False: # -1
2793 return self.get_or_make_index_from_constant(1)
2794 if arg == ~True: # -2
2795 return self.get_or_make_index_from_constant(0)
2796 arg = cmh.assert_is_zero_or_one(arg)
2797 return self.get_or_make_index_from_constant(arg)
2798 if cmh.is_boolean(arg):
2799 return self.get_or_make_index_from_constant(int(arg))
2800 raise TypeError(f"not supported: model.get_or_make_boolean_index({arg})")
2801
2802 def get_interval_index(self, arg: IntervalVar) -> int:
2803 if not isinstance(arg, IntervalVar):
2804 raise TypeError("NotSupported: model.get_interval_index(%s)" % arg)
2805 return arg.index
2806
2807 def get_or_make_index_from_constant(self, value: IntegralT) -> int:
2808 if value in self.__constant_map:
2809 return self.__constant_map[value]
2810 index = len(self.__model.variables)
2811 self.__model.variables.add(domain=[value, value])
2812 self.__constant_map[value] = index
2813 return index
2814
2816 self, var_index: int
2817 ) -> cp_model_pb2.IntegerVariableProto:
2818 if var_index >= 0:
2819 return self.__model.variables[var_index]
2820 else:
2821 return self.__model.variables[-var_index - 1]
2822
2824 self, linear_expr: LinearExprT, negate: bool = False
2825 ) -> cp_model_pb2.LinearExpressionProto:
2826 """Returns a LinearExpressionProto built from a LinearExpr instance."""
2827 result: cp_model_pb2.LinearExpressionProto = (
2828 cp_model_pb2.LinearExpressionProto()
2829 )
2830 mult = -1 if negate else 1
2831 if isinstance(linear_expr, IntegralTypes):
2832 result.offset = int(linear_expr) * mult
2833 return result
2834
2835 if isinstance(linear_expr, IntVar):
2836 result.vars.append(self.get_or_make_index(linear_expr))
2837 result.coeffs.append(mult)
2838 return result
2839
2840 coeffs_map, constant = cast(LinearExpr, linear_expr).get_integer_var_value_map()
2841 result.offset = constant * mult
2842 for t in coeffs_map.items():
2843 if not isinstance(t[0], IntVar):
2844 raise TypeError("Wrong argument" + str(t))
2845 c = cmh.assert_is_int64(t[1])
2846 result.vars.append(t[0].index)
2847 result.coeffs.append(c * mult)
2848 return result
2849
2850 def _set_objective(self, obj: ObjLinearExprT, minimize: bool):
2851 """Sets the objective of the model."""
2852 self.clear_objective()
2853 if isinstance(obj, IntVar):
2854 self.__model.objective.vars.append(obj.index)
2855 self.__model.objective.offset = 0
2856 if minimize:
2857 self.__model.objective.coeffs.append(1)
2858 self.__model.objective.scaling_factor = 1
2859 else:
2860 self.__model.objective.coeffs.append(-1)
2861 self.__model.objective.scaling_factor = -1
2862 elif isinstance(obj, LinearExpr):
2863 coeffs_map, constant, is_integer = obj.get_float_var_value_map()
2864 if is_integer:
2865 if minimize:
2866 self.__model.objective.scaling_factor = 1
2867 self.__model.objective.offset = constant
2868 else:
2869 self.__model.objective.scaling_factor = -1
2870 self.__model.objective.offset = -constant
2871 for v, c in coeffs_map.items():
2872 c_as_int = int(c)
2873 self.__model.objective.vars.append(v.index)
2874 if minimize:
2875 self.__model.objective.coeffs.append(c_as_int)
2876 else:
2877 self.__model.objective.coeffs.append(-c_as_int)
2878 else:
2879 self.__model.floating_point_objective.maximize = not minimize
2880 self.__model.floating_point_objective.offset = constant
2881 for v, c in coeffs_map.items():
2882 self.__model.floating_point_objective.coeffs.append(c)
2883 self.__model.floating_point_objective.vars.append(v.index)
2884 elif isinstance(obj, IntegralTypes):
2885 self.__model.objective.offset = int(obj)
2886 self.__model.objective.scaling_factor = 1
2887 else:
2888 raise TypeError("TypeError: " + str(obj) + " is not a valid objective")
2889
2890 def minimize(self, obj: ObjLinearExprT):
2891 """Sets the objective of the model to minimize(obj)."""
2892 self._set_objective(obj, minimize=True)
2893
2894 def maximize(self, obj: ObjLinearExprT):
2895 """Sets the objective of the model to maximize(obj)."""
2896 self._set_objective(obj, minimize=False)
2897
2898 def has_objective(self) -> bool:
2899 return self.__model.HasField("objective") or self.__model.HasField(
2900 "floating_point_objective"
2901 )
2902
2904 self.__model.ClearField("objective")
2905 self.__model.ClearField("floating_point_objective")
2906
2908 self,
2909 variables: Sequence[IntVar],
2910 var_strategy: cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy,
2911 domain_strategy: cp_model_pb2.DecisionStrategyProto.DomainReductionStrategy,
2912 ) -> None:
2913 """Adds a search strategy to the model.
2914
2915 Args:
2916 variables: a list of variables this strategy will assign.
2917 var_strategy: heuristic to choose the next variable to assign.
2918 domain_strategy: heuristic to reduce the domain of the selected variable.
2919 Currently, this is advanced code: the union of all strategies added to
2920 the model must be complete, i.e. instantiates all variables. Otherwise,
2921 solve() will fail.
2922 """
2923
2924 strategy: cp_model_pb2.DecisionStrategyProto = (
2925 self.__model.search_strategy.add()
2926 )
2927 for v in variables:
2928 expr = strategy.exprs.add()
2929 if v.index >= 0:
2930 expr.vars.append(v.index)
2931 expr.coeffs.append(1)
2932 else:
2933 expr.vars.append(self.negated(v.index))
2934 expr.coeffs.append(-1)
2935 expr.offset = 1
2936
2937 strategy.variable_selection_strategy = var_strategy
2938 strategy.domain_reduction_strategy = domain_strategy
2939
2940 def model_stats(self) -> str:
2941 """Returns a string containing some model statistics."""
2942 return swig_helper.CpSatHelper.model_stats(self.__model)
2943
2944 def validate(self) -> str:
2945 """Returns a string indicating that the model is invalid."""
2946 return swig_helper.CpSatHelper.validate_model(self.__model)
2947
2948 def export_to_file(self, file: str) -> bool:
2949 """Write the model as a protocol buffer to 'file'.
2950
2951 Args:
2952 file: file to write the model to. If the filename ends with 'txt', the
2953 model will be written as a text file, otherwise, the binary format will
2954 be used.
2955
2956 Returns:
2957 True if the model was correctly written.
2958 """
2959 return swig_helper.CpSatHelper.write_model_to_file(self.__model, file)
2960
2961 def add_hint(self, var: IntVar, value: int) -> None:
2962 """Adds 'var == value' as a hint to the solver."""
2963 self.__model.solution_hint.vars.append(self.get_or_make_index(var))
2964 self.__model.solution_hint.values.append(value)
2965
2966 def clear_hints(self):
2967 """Removes any solution hint from the model."""
2968 self.__model.ClearField("solution_hint")
2969
2970 def add_assumption(self, lit: LiteralT) -> None:
2971 """Adds the literal to the model as assumptions."""
2972 self.__model.assumptions.append(self.get_or_make_boolean_index(lit))
2973
2974 def add_assumptions(self, literals: Iterable[LiteralT]) -> None:
2975 """Adds the literals to the model as assumptions."""
2976 for lit in literals:
2977 self.add_assumption(lit)
2978
2979 def clear_assumptions(self) -> None:
2980 """Removes all assumptions from the model."""
2981 self.__model.ClearField("assumptions")
2982
2983 # Helpers.
2984 def assert_is_boolean_variable(self, x: LiteralT) -> None:
2985 if isinstance(x, IntVar):
2986 var = self.__model.variables[x.index]
2987 if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1:
2988 raise TypeError("TypeError: " + str(x) + " is not a boolean variable")
2989 elif not isinstance(x, _NotBooleanVariable):
2990 raise TypeError("TypeError: " + str(x) + " is not a boolean variable")
2991
2992 # Compatibility with pre PEP8
2993 # pylint: disable=invalid-name
2994
2995 def Name(self) -> str:
2996 return self.namenamename
2997
2998 def SetName(self, name: str) -> None:
2999 self.namenamename = name
3000
3001 def Proto(self) -> cp_model_pb2.CpModelProto:
3002 return self.protoproto
3003
3004 NewIntVar = new_int_var
3005 NewIntVarFromDomain = new_int_var_from_domain
3006 NewBoolVar = new_bool_var
3007 NewConstant = new_constant
3008 NewIntVarSeries = new_int_var_series
3009 NewBoolVarSeries = new_bool_var_series
3010 AddLinearConstraint = add_linear_constraint
3011 AddLinearExpressionInDomain = add_linear_expression_in_domain
3012 Add = add
3013 AddAllDifferent = add_all_different
3014 AddElement = add_element
3015 AddCircuit = add_circuit
3016 AddMultipleCircuit = add_multiple_circuit
3017 AddAllowedAssignments = add_allowed_assignments
3018 AddForbiddenAssignments = add_forbidden_assignments
3019 AddAutomaton = add_automaton
3020 AddInverse = add_inverse
3021 AddReservoirConstraint = add_reservoir_constraint
3022 AddReservoirConstraintWithActive = add_reservoir_constraint_with_active
3023 AddImplication = add_implication
3024 AddBoolOr = add_bool_or
3025 AddAtLeastOne = add_at_least_one
3026 AddAtMostOne = add_at_most_one
3027 AddExactlyOne = add_exactly_one
3028 AddBoolAnd = add_bool_and
3029 AddBoolXOr = add_bool_xor
3030 AddMinEquality = add_min_equality
3031 AddMaxEquality = add_max_equality
3032 AddDivisionEquality = add_division_equality
3033 AddAbsEquality = add_abs_equality
3034 AddModuloEquality = add_modulo_equality
3035 AddMultiplicationEquality = add_multiplication_equality
3036 NewIntervalVar = new_interval_var
3037 NewIntervalVarSeries = new_interval_var_series
3038 NewFixedSizeIntervalVar = new_fixed_size_interval_var
3039 NewOptionalIntervalVar = new_optional_interval_var
3040 NewOptionalIntervalVarSeries = new_optional_interval_var_series
3041 NewOptionalFixedSizeIntervalVar = new_optional_fixed_size_interval_var
3042 NewOptionalFixedSizeIntervalVarSeries = new_optional_fixed_size_interval_var_series
3043 AddNoOverlap = add_no_overlap
3044 AddNoOverlap2D = add_no_overlap_2d
3045 AddCumulative = add_cumulative
3046 Clone = clone
3047 GetBoolVarFromProtoIndex = get_bool_var_from_proto_index
3048 GetIntVarFromProtoIndex = get_int_var_from_proto_index
3049 GetIntervalVarFromProtoIndex = get_interval_var_from_proto_index
3050 Minimize = minimize
3051 Maximize = maximize
3052 HasObjective = has_objective
3053 ClearObjective = clear_objective
3054 AddDecisionStrategy = add_decision_strategy
3055 ModelStats = model_stats
3056 Validate = validate
3057 ExportToFile = export_to_file
3058 AddHint = add_hint
3059 ClearHints = clear_hints
3060 AddAssumption = add_assumption
3061 AddAssumptions = add_assumptions
3062 ClearAssumptions = clear_assumptions
3063
3064 # pylint: enable=invalid-name
3065
3066
3067@overload
3069 args: Union[Tuple[LiteralT, ...], Iterable[LiteralT]]
3070) -> Union[Iterable[LiteralT], LiteralT]: ...
3071
3072
3073@overload
3075 args: Union[Tuple[LinearExprT, ...], Iterable[LinearExprT]]
3076) -> Union[Iterable[LinearExprT], LinearExprT]: ...
3077
3078
3080 if hasattr(args, "__len__"): # Tuple
3081 if len(args) != 1:
3082 return args
3083 if isinstance(args[0], (NumberTypes, LinearExpr)):
3084 return args
3085 # Generator
3086 return args[0]
3087
3088
3090 expression: LinearExprT, solution: cp_model_pb2.CpSolverResponse
3091) -> int:
3092 """Evaluate a linear expression against a solution."""
3093 if isinstance(expression, IntegralTypes):
3094 return int(expression)
3095 if not isinstance(expression, LinearExpr):
3096 raise TypeError("Cannot interpret %s as a linear expression." % expression)
3097
3098 value = 0
3099 to_process = [(expression, 1)]
3100 while to_process:
3101 expr, coeff = to_process.pop()
3102 if isinstance(expr, IntegralTypes):
3103 value += int(expr) * coeff
3104 elif isinstance(expr, _ProductCst):
3105 to_process.append((expr.expression(), coeff * expr.coefficient()))
3106 elif isinstance(expr, _Sum):
3107 to_process.append((expr.left(), coeff))
3108 to_process.append((expr.right(), coeff))
3109 elif isinstance(expr, _SumArray):
3110 for e in expr.expressions():
3111 to_process.append((e, coeff))
3112 value += expr.constant() * coeff
3113 elif isinstance(expr, _WeightedSum):
3114 for e, c in zip(expr.expressions(), expr.coefficients()):
3115 to_process.append((e, coeff * c))
3116 value += expr.constant() * coeff
3117 elif isinstance(expr, IntVar):
3118 value += coeff * solution.solution[expr.index]
3119 elif isinstance(expr, _NotBooleanVariable):
3120 value += coeff * (1 - solution.solution[expr.negated().index])
3121 else:
3122 raise TypeError(f"Cannot interpret {expr} as a linear expression.")
3123
3124 return value
3125
3126
3128 literal: LiteralT, solution: cp_model_pb2.CpSolverResponse
3129) -> bool:
3130 """Evaluate a boolean expression against a solution."""
3131 if isinstance(literal, IntegralTypes):
3132 return bool(literal)
3133 elif isinstance(literal, IntVar) or isinstance(literal, _NotBooleanVariable):
3134 index: int = cast(Union[IntVar, _NotBooleanVariable], literal).index
3135 if index >= 0:
3136 return bool(solution.solution[index])
3137 else:
3138 return not solution.solution[-index - 1]
3139 else:
3140 raise TypeError(f"Cannot interpret {literal} as a boolean expression.")
3141
3142
3144 """Main solver class.
3145
3146 The purpose of this class is to search for a solution to the model provided
3147 to the solve() method.
3148
3149 Once solve() is called, this class allows inspecting the solution found
3150 with the value() and boolean_value() methods, as well as general statistics
3151 about the solve procedure.
3152 """
3153
3154 def __init__(self) -> None:
3155 self.__solution: Optional[cp_model_pb2.CpSolverResponse] = None
3156 self.parameters: sat_parameters_pb2.SatParameters = (
3157 sat_parameters_pb2.SatParameters()
3158 )
3159 self.log_callback: Optional[Callable[[str], None]] = None
3160 self.best_bound_callback: Optional[Callable[[float], None]] = None
3161 self.__solve_wrapper: Optional[swig_helper.SolveWrapper] = None
3162 self.__lock: threading.Lock = threading.Lock()
3163
3165 self,
3166 model: CpModel,
3167 solution_callback: Optional["CpSolverSolutionCallback"] = None,
3168 ) -> cp_model_pb2.CpSolverStatus:
3169 """Solves a problem and passes each solution to the callback if not null."""
3170 with self.__lock:
3171 self.__solve_wrapper = swig_helper.SolveWrapper()
3172
3173 self.__solve_wrapper.set_parameters(self.parameters)
3174 if solution_callback is not None:
3175 self.__solve_wrapper.add_solution_callback(solution_callback)
3176
3177 if self.log_callback is not None:
3178 self.__solve_wrapper.add_log_callback(self.log_callback)
3179
3180 if self.best_bound_callback is not None:
3181 self.__solve_wrapper.add_best_bound_callback(self.best_bound_callback)
3182
3183 solution: cp_model_pb2.CpSolverResponse = self.__solve_wrapper.solve(
3184 model.proto
3185 )
3186 self.__solution = solution
3187
3188 if solution_callback is not None:
3189 self.__solve_wrapper.clear_solution_callback(solution_callback)
3190
3191 with self.__lock:
3192 self.__solve_wrapper = None
3193
3194 return solution.status
3195
3196 def stop_search(self) -> None:
3197 """Stops the current search asynchronously."""
3198 with self.__lock:
3199 if self.__solve_wrapper:
3201
3202 def value(self, expression: LinearExprT) -> int:
3203 """Returns the value of a linear expression after solve."""
3205
3206 def values(self, variables: _IndexOrSeries) -> pd.Series:
3207 """Returns the values of the input variables.
3208
3209 If `variables` is a `pd.Index`, then the output will be indexed by the
3210 variables. If `variables` is a `pd.Series` indexed by the underlying
3211 dimensions, then the output will be indexed by the same underlying
3212 dimensions.
3213
3214 Args:
3215 variables (Union[pd.Index, pd.Series]): The set of variables from which to
3216 get the values.
3217
3218 Returns:
3219 pd.Series: The values of all variables in the set.
3220 """
3221 solution = self._solution_solution
3222 return _attribute_series(
3223 func=lambda v: solution.solution[v.index],
3224 values=variables,
3225 )
3226
3227 def boolean_value(self, literal: LiteralT) -> bool:
3228 """Returns the boolean value of a literal after solve."""
3230
3231 def boolean_values(self, variables: _IndexOrSeries) -> pd.Series:
3232 """Returns the values of the input variables.
3233
3234 If `variables` is a `pd.Index`, then the output will be indexed by the
3235 variables. If `variables` is a `pd.Series` indexed by the underlying
3236 dimensions, then the output will be indexed by the same underlying
3237 dimensions.
3238
3239 Args:
3240 variables (Union[pd.Index, pd.Series]): The set of variables from which to
3241 get the values.
3242
3243 Returns:
3244 pd.Series: The values of all variables in the set.
3245 """
3246 solution = self._solution_solution
3247 return _attribute_series(
3248 func=lambda literal: evaluate_boolean_expression(literal, solution),
3249 values=variables,
3250 )
3251
3252 @property
3253 def objective_value(self) -> float:
3254 """Returns the value of the objective after solve."""
3255 return self._solution_solution.objective_value
3256
3257 @property
3258 def best_objective_bound(self) -> float:
3259 """Returns the best lower (upper) bound found when min(max)imizing."""
3260 return self._solution_solution.best_objective_bound
3261
3262 @property
3263 def num_booleans(self) -> int:
3264 """Returns the number of boolean variables managed by the SAT solver."""
3265 return self._solution_solution.num_booleans
3266
3267 @property
3268 def num_conflicts(self) -> int:
3269 """Returns the number of conflicts since the creation of the solver."""
3270 return self._solution_solution.num_conflicts
3271
3272 @property
3273 def num_branches(self) -> int:
3274 """Returns the number of search branches explored by the solver."""
3275 return self._solution_solution.num_branches
3276
3277 @property
3278 def wall_time(self) -> float:
3279 """Returns the wall time in seconds since the creation of the solver."""
3280 return self._solution_solution.wall_time
3281
3282 @property
3283 def user_time(self) -> float:
3284 """Returns the user time in seconds since the creation of the solver."""
3285 return self._solution_solution.user_time
3286
3287 @property
3288 def response_proto(self) -> cp_model_pb2.CpSolverResponse:
3289 """Returns the response object."""
3290 return self._solution_solution
3291
3292 def response_stats(self) -> str:
3293 """Returns some statistics on the solution found as a string."""
3294 return swig_helper.CpSatHelper.solver_response_stats(self._solution_solution)
3295
3297 """Returns the indices of the infeasible assumptions."""
3298 return self._solution_solution.sufficient_assumptions_for_infeasibility
3299
3300 def status_name(self, status: Optional[Any] = None) -> str:
3301 """Returns the name of the status returned by solve()."""
3302 if status is None:
3303 status = self._solution_solution.status
3304 return cp_model_pb2.CpSolverStatus.Name(status)
3305
3306 def solution_info(self) -> str:
3307 """Returns some information on the solve process.
3308
3309 Returns some information on how the solution was found, or the reason
3310 why the model or the parameters are invalid.
3311
3312 Raises:
3313 RuntimeError: if solve() has not been called.
3314 """
3315 return self._solution_solution.solution_info
3316
3317 @property
3318 def _solution(self) -> cp_model_pb2.CpSolverResponse:
3319 """Checks solve() has been called, and returns the solution."""
3320 if self.__solution is None:
3321 raise RuntimeError("solve() has not been called.")
3322 return self.__solution
3323
3324 # Compatibility with pre PEP8
3325 # pylint: disable=invalid-name
3326
3327 def BestObjectiveBound(self) -> float:
3328 return self.best_objective_bound
3329
3330 def BooleanValue(self, literal: LiteralT) -> bool:
3331 return self.boolean_value(literal)
3332
3333 def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series:
3334 return self.boolean_values(variables)
3335
3336 def NumBooleans(self) -> int:
3337 return self.num_booleans
3338
3339 def NumConflicts(self) -> int:
3340 return self.num_conflicts
3341
3342 def NumBranches(self) -> int:
3343 return self.num_branches
3344
3345 def ObjectiveValue(self) -> float:
3346 return self.objective_value
3347
3348 def ResponseProto(self) -> cp_model_pb2.CpSolverResponse:
3349 return self.response_proto
3350
3351 def ResponseStats(self) -> str:
3352 return self.response_stats()
3353
3355 self,
3356 model: CpModel,
3357 solution_callback: Optional["CpSolverSolutionCallback"] = None,
3358 ) -> cp_model_pb2.CpSolverStatus:
3359 return self.solve(model, solution_callback)
3360
3361 def SolutionInfo(self) -> str:
3362 return self.solution_info()
3363
3364 def StatusName(self, status: Optional[Any] = None) -> str:
3365 return self.status_name(status)
3366
3367 def StopSearch(self) -> None:
3368 self.stop_search()
3369
3370 def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]:
3372
3373 def UserTime(self) -> float:
3374 return self.user_time
3375
3376 def Value(self, expression: LinearExprT) -> int:
3377 return self.value(expression)
3378
3379 def Values(self, variables: _IndexOrSeries) -> pd.Series:
3380 return self.values(variables)
3381
3382 def WallTime(self) -> float:
3383 return self.wall_time
3384
3386 self, model: CpModel, callback: "CpSolverSolutionCallback"
3387 ) -> cp_model_pb2.CpSolverStatus:
3388 """DEPRECATED Use solve() with the callback argument."""
3389 warnings.warn(
3390 "solve_with_solution_callback is deprecated; use solve() with"
3391 + "the callback argument.",
3392 DeprecationWarning,
3393 )
3394 return self.solve(model, callback)
3395
3397 self, model: CpModel, callback: "CpSolverSolutionCallback"
3398 ) -> cp_model_pb2.CpSolverStatus:
3399 """DEPRECATED Use solve() with the right parameter.
3400
3401 Search for all solutions of a satisfiability problem.
3402
3403 This method searches for all feasible solutions of a given model.
3404 Then it feeds the solution to the callback.
3405
3406 Note that the model cannot contain an objective.
3407
3408 Args:
3409 model: The model to solve.
3410 callback: The callback that will be called at each solution.
3411
3412 Returns:
3413 The status of the solve:
3414
3415 * *FEASIBLE* if some solutions have been found
3416 * *INFEASIBLE* if the solver has proved there are no solution
3417 * *OPTIMAL* if all solutions have been found
3418 """
3419 warnings.warn(
3420 "search_for_all_solutions is deprecated; use solve() with"
3421 + "enumerate_all_solutions = True.",
3422 DeprecationWarning,
3423 )
3424 if model.has_objective():
3425 raise TypeError(
3426 "Search for all solutions is only defined on satisfiability problems"
3427 )
3428 # Store old parameter.
3429 enumerate_all = self.parameters.enumerate_all_solutions
3430 self.parameters.enumerate_all_solutions = True
3431
3432 status: cp_model_pb2.CpSolverStatus = self.solve(model, callback)
3433
3434 # Restore parameter.
3435 self.parameters.enumerate_all_solutions = enumerate_all
3436 return status
3437
3438
3439# pylint: enable=invalid-name
3440
3441
3442class CpSolverSolutionCallback(swig_helper.SolutionCallback):
3443 """Solution callback.
3444
3445 This class implements a callback that will be called at each new solution
3446 found during search.
3447
3448 The method on_solution_callback() will be called by the solver, and must be
3449 implemented. The current solution can be queried using the boolean_value()
3450 and value() methods.
3451
3452 These methods returns the same information as their counterpart in the
3453 `CpSolver` class.
3454 """
3455
3456 def __init__(self) -> None:
3457 swig_helper.SolutionCallback.__init__(self)
3458
3459 def OnSolutionCallback(self) -> None:
3460 """Proxy for the same method in snake case."""
3461 self.on_solution_callback()
3462
3463 def boolean_value(self, lit: LiteralT) -> bool:
3464 """Returns the boolean value of a boolean literal.
3465
3466 Args:
3467 lit: A boolean variable or its negation.
3468
3469 Returns:
3470 The Boolean value of the literal in the solution.
3471
3472 Raises:
3473 RuntimeError: if `lit` is not a boolean variable or its negation.
3474 """
3475 if not self.has_response():
3476 raise RuntimeError("solve() has not been called.")
3477 if isinstance(lit, IntegralTypes):
3478 return bool(lit)
3479 if isinstance(lit, IntVar) or isinstance(lit, _NotBooleanVariable):
3480 return self.SolutionBooleanValue(
3481 cast(Union[IntVar, _NotBooleanVariable], lit).index
3482 )
3483 if cmh.is_boolean(lit):
3484 return bool(lit)
3485 raise TypeError(f"Cannot interpret {lit} as a boolean expression.")
3486
3487 def value(self, expression: LinearExprT) -> int:
3488 """Evaluates an linear expression in the current solution.
3489
3490 Args:
3491 expression: a linear expression of the model.
3492
3493 Returns:
3494 An integer value equal to the evaluation of the linear expression
3495 against the current solution.
3496
3497 Raises:
3498 RuntimeError: if 'expression' is not a LinearExpr.
3499 """
3500 if not self.has_response():
3501 raise RuntimeError("solve() has not been called.")
3502
3503 value = 0
3504 to_process = [(expression, 1)]
3505 while to_process:
3506 expr, coeff = to_process.pop()
3507 if isinstance(expr, IntegralTypes):
3508 value += int(expr) * coeff
3509 elif isinstance(expr, _ProductCst):
3510 to_process.append((expr.expression(), coeff * expr.coefficient()))
3511 elif isinstance(expr, _Sum):
3512 to_process.append((expr.left(), coeff))
3513 to_process.append((expr.right(), coeff))
3514 elif isinstance(expr, _SumArray):
3515 for e in expr.expressions():
3516 to_process.append((e, coeff))
3517 value += expr.constant() * coeff
3518 elif isinstance(expr, _WeightedSum):
3519 for e, c in zip(expr.expressions(), expr.coefficients()):
3520 to_process.append((e, coeff * c))
3521 value += expr.constant() * coeff
3522 elif isinstance(expr, IntVar):
3523 value += coeff * self.SolutionIntegerValue(expr.index)
3524 elif isinstance(expr, _NotBooleanVariable):
3525 value += coeff * (1 - self.SolutionIntegerValue(expr.negated().index))
3526 else:
3527 raise TypeError(
3528 f"cannot interpret {expression} as a linear expression."
3529 )
3530
3531 return value
3532
3533 def has_response(self) -> bool:
3534 return self.HasResponse()
3535
3536 def stop_search(self) -> None:
3537 """Stops the current search asynchronously."""
3538 if not self.has_response():
3539 raise RuntimeError("solve() has not been called.")
3540 self.StopSearch()
3541
3542 @property
3543 def objective_value(self) -> float:
3544 """Returns the value of the objective after solve."""
3545 if not self.has_response():
3546 raise RuntimeError("solve() has not been called.")
3547 return self.ObjectiveValue()
3548
3549 @property
3550 def best_objective_bound(self) -> float:
3551 """Returns the best lower (upper) bound found when min(max)imizing."""
3552 if not self.has_response():
3553 raise RuntimeError("solve() has not been called.")
3554 return self.BestObjectiveBound()
3555
3556 @property
3557 def num_booleans(self) -> int:
3558 """Returns the number of boolean variables managed by the SAT solver."""
3559 if not self.has_response():
3560 raise RuntimeError("solve() has not been called.")
3561 return self.NumBooleans()
3562
3563 @property
3564 def num_conflicts(self) -> int:
3565 """Returns the number of conflicts since the creation of the solver."""
3566 if not self.has_response():
3567 raise RuntimeError("solve() has not been called.")
3568 return self.NumConflicts()
3569
3570 @property
3571 def num_branches(self) -> int:
3572 """Returns the number of search branches explored by the solver."""
3573 if not self.has_response():
3574 raise RuntimeError("solve() has not been called.")
3575 return self.NumBranches()
3576
3577 @property
3579 """Returns the number of integer propagations done by the solver."""
3580 if not self.has_response():
3581 raise RuntimeError("solve() has not been called.")
3582 return self.NumIntegerPropagations()
3583
3584 @property
3586 """Returns the number of Boolean propagations done by the solver."""
3587 if not self.has_response():
3588 raise RuntimeError("solve() has not been called.")
3589 return self.NumBooleanPropagations()
3590
3591 @property
3592 def deterministic_time(self) -> float:
3593 """Returns the determistic time in seconds since the creation of the solver."""
3594 if not self.has_response():
3595 raise RuntimeError("solve() has not been called.")
3596 return self.DeterministicTime()
3597
3598 @property
3599 def wall_time(self) -> float:
3600 """Returns the wall time in seconds since the creation of the solver."""
3601 if not self.has_response():
3602 raise RuntimeError("solve() has not been called.")
3603 return self.WallTime()
3604
3605 @property
3606 def user_time(self) -> float:
3607 """Returns the user time in seconds since the creation of the solver."""
3608 if not self.has_response():
3609 raise RuntimeError("solve() has not been called.")
3610 return self.UserTime()
3611
3612 @property
3613 def response_proto(self) -> cp_model_pb2.CpSolverResponse:
3614 """Returns the response object."""
3615 if not self.has_response():
3616 raise RuntimeError("solve() has not been called.")
3617 return self.Response()
3618
3619 # Compatibility with pre PEP8
3620 # pylint: disable=invalid-name
3621 Value = value
3622 BooleanValue = boolean_value
3623 # pylint: enable=invalid-name
3624
3625
3627 """Display the objective value and time of intermediate solutions."""
3628
3629 def __init__(self) -> None:
3630 CpSolverSolutionCallback.__init__(self)
3631 self.__solution_count = 0
3632 self.__start_time = time.time()
3633
3634 def on_solution_callback(self) -> None:
3635 """Called on each new solution."""
3636 current_time = time.time()
3637 obj = self.objective_value
3638 print(
3639 "Solution %i, time = %0.2f s, objective = %i"
3640 % (self.__solution_count, current_time - self.__start_time, obj)
3641 )
3642 self.__solution_count += 1
3643
3644 def solution_count(self) -> int:
3645 """Returns the number of solutions found."""
3646 return self.__solution_count
3647
3648
3650 """Print intermediate solutions (objective, variable values, time)."""
3651
3652 def __init__(self, variables: Sequence[IntVar]) -> None:
3653 CpSolverSolutionCallback.__init__(self)
3654 self.__variables__variables: Sequence[IntVar] = variables
3655 self.__solution_count: int = 0
3656 self.__start_time: float = time.time()
3657
3658 def on_solution_callback(self) -> None:
3659 """Called on each new solution."""
3660 current_time = time.time()
3661 obj = self.objective_value
3662 print(
3663 "Solution %i, time = %0.2f s, objective = %i"
3664 % (self.__solution_count, current_time - self.__start_time, obj)
3665 )
3667 print(" %s = %i" % (v, self.value(v)), end=" ")
3668 print()
3669 self.__solution_count += 1
3670
3671 @property
3672 def solution_count(self) -> int:
3673 """Returns the number of solutions found."""
3674 return self.__solution_count
3675
3676
3678 """Print intermediate solutions (variable values, time)."""
3679
3680 def __init__(self, variables: Sequence[IntVar]) -> None:
3681 CpSolverSolutionCallback.__init__(self)
3682 self.__variables__variables: Sequence[IntVar] = variables
3683 self.__solution_count: int = 0
3684 self.__start_time: float = time.time()
3685
3686 def on_solution_callback(self) -> None:
3687 """Called on each new solution."""
3688 current_time = time.time()
3689 print(
3690 "Solution %i, time = %0.2f s"
3691 % (self.__solution_count, current_time - self.__start_time)
3692 )
3694 print(" %s = %i" % (v, self.value(v)), end=" ")
3695 print()
3696 self.__solution_count += 1
3697
3698 @property
3699 def solution_count(self) -> int:
3700 """Returns the number of solutions found."""
3701 return self.__solution_count
3702
3703
3704def _get_index(obj: _IndexOrSeries) -> pd.Index:
3705 """Returns the indices of `obj` as a `pd.Index`."""
3706 if isinstance(obj, pd.Series):
3707 return obj.index
3708 return obj
3709
3710
3712 *,
3713 func: Callable[[IntVar], IntegralT],
3714 values: _IndexOrSeries,
3715) -> pd.Series:
3716 """Returns the attributes of `values`.
3717
3718 Args:
3719 func: The function to call for getting the attribute data.
3720 values: The values that the function will be applied (element-wise) to.
3721
3722 Returns:
3723 pd.Series: The attribute values.
3724 """
3725 return pd.Series(
3726 data=[func(v) for v in values],
3727 index=_get_index(values),
3728 )
3729
3730
3732 value_or_series: Union[IntegralT, pd.Series], index: pd.Index
3733) -> pd.Series:
3734 """Returns a pd.Series of the given index with the corresponding values.
3735
3736 Args:
3737 value_or_series: the values to be converted (if applicable).
3738 index: the index of the resulting pd.Series.
3739
3740 Returns:
3741 pd.Series: The set of values with the given index.
3742
3743 Raises:
3744 TypeError: If the type of `value_or_series` is not recognized.
3745 ValueError: If the index does not match.
3746 """
3747 if isinstance(value_or_series, IntegralTypes):
3748 result = pd.Series(data=value_or_series, index=index)
3749 elif isinstance(value_or_series, pd.Series):
3750 if value_or_series.index.equals(index):
3751 result = value_or_series
3752 else:
3753 raise ValueError("index does not match")
3754 else:
3755 raise TypeError("invalid type={}".format(type(value_or_series)))
3756 return result
3757
3758
3760 value_or_series: Union[LinearExprT, pd.Series], index: pd.Index
3761) -> pd.Series:
3762 """Returns a pd.Series of the given index with the corresponding values.
3763
3764 Args:
3765 value_or_series: the values to be converted (if applicable).
3766 index: the index of the resulting pd.Series.
3767
3768 Returns:
3769 pd.Series: The set of values with the given index.
3770
3771 Raises:
3772 TypeError: If the type of `value_or_series` is not recognized.
3773 ValueError: If the index does not match.
3774 """
3775 if isinstance(value_or_series, IntegralTypes):
3776 result = pd.Series(data=value_or_series, index=index)
3777 elif isinstance(value_or_series, pd.Series):
3778 if value_or_series.index.equals(index):
3779 result = value_or_series
3780 else:
3781 raise ValueError("index does not match")
3782 else:
3783 raise TypeError("invalid type={}".format(type(value_or_series)))
3784 return result
3785
3786
3788 value_or_series: Union[LiteralT, pd.Series], index: pd.Index
3789) -> pd.Series:
3790 """Returns a pd.Series of the given index with the corresponding values.
3791
3792 Args:
3793 value_or_series: the values to be converted (if applicable).
3794 index: the index of the resulting pd.Series.
3795
3796 Returns:
3797 pd.Series: The set of values with the given index.
3798
3799 Raises:
3800 TypeError: If the type of `value_or_series` is not recognized.
3801 ValueError: If the index does not match.
3802 """
3803 if isinstance(value_or_series, IntegralTypes):
3804 result = pd.Series(data=value_or_series, index=index)
3805 elif isinstance(value_or_series, pd.Series):
3806 if value_or_series.index.equals(index):
3807 result = value_or_series
3808 else:
3809 raise ValueError("index does not match")
3810 else:
3811 raise TypeError("invalid type={}".format(type(value_or_series)))
3812 return result
None __init__(self, LinearExprT expr, Sequence[int] bounds)
Definition cp_model.py:999
None __init__(self, "CpModel" cp_model)
Definition cp_model.py:1081
cp_model_pb2.ConstraintProto proto(self)
Definition cp_model.py:1150
cp_model_pb2.ConstraintProto Proto(self)
Definition cp_model.py:1165
"Constraint" only_enforce_if(self, Iterable[LiteralT] boolvar)
Definition cp_model.py:1089
"Constraint" with_name(self, str name)
Definition cp_model.py:1129
Constraint add_no_overlap_2d(self, Iterable[IntervalVar] x_intervals, Iterable[IntervalVar] y_intervals)
Definition cp_model.py:2641
cp_model_pb2.CpModelProto proto(self)
Definition cp_model.py:2761
IntVar new_constant(self, IntegralT value)
Definition cp_model.py:1401
IntervalVar new_optional_interval_var(self, LinearExprT start, LinearExprT size, LinearExprT end, LiteralT is_present, str name)
Definition cp_model.py:2424
Constraint add_no_overlap(self, Iterable[IntervalVar] interval_vars)
Definition cp_model.py:2618
bool export_to_file(self, str file)
Definition cp_model.py:2948
Constraint add_bool_or(self, *LiteralT literals)
Definition cp_model.py:2060
IntervalVar get_interval_var_from_proto_index(self, int index)
Definition cp_model.py:2740
maximize(self, ObjLinearExprT obj)
Definition cp_model.py:2894
minimize(self, ObjLinearExprT obj)
Definition cp_model.py:2890
IntVar new_int_var_from_domain(self, sorted_interval_list.Domain domain, str name)
Definition cp_model.py:1381
pd.Series new_bool_var_series(self, str name, pd.Index index)
Definition cp_model.py:1473
Constraint add_element(self, VariableT index, Sequence[VariableT] variables, VariableT target)
Definition cp_model.py:1590
pd.Series new_int_var_series(self, str name, pd.Index index, Union[IntegralT, pd.Series] lower_bounds, Union[IntegralT, pd.Series] upper_bounds)
Definition cp_model.py:1411
Constraint add_at_most_one(self, Iterable[LiteralT] literals)
Definition cp_model.py:2085
Constraint add_automaton(self, Sequence[VariableT] transition_variables, IntegralT starting_state, Sequence[IntegralT] final_states, Sequence[Tuple[IntegralT, IntegralT, IntegralT]] transition_triples)
Definition cp_model.py:1776
add_map_domain(self, IntVar var, Iterable[IntVar] bool_var_array, IntegralT offset=0)
Definition cp_model.py:2026
pd.Series new_interval_var_series(self, str name, pd.Index index, Union[LinearExprT, pd.Series] starts, Union[LinearExprT, pd.Series] sizes, Union[LinearExprT, pd.Series] ends)
Definition cp_model.py:2300
IntVar new_int_var(self, IntegralT lb, IntegralT ub, str name)
Definition cp_model.py:1361
None add_assumptions(self, Iterable[LiteralT] literals)
Definition cp_model.py:2974
Constraint add_bool_or(self, Iterable[LiteralT] literals)
Definition cp_model.py:2057
Constraint add_bool_xor(self, Iterable[LiteralT] literals)
Definition cp_model.py:2139
IntVar get_bool_var_from_proto_index(self, int index)
Definition cp_model.py:2717
cp_model_pb2.CpModelProto Proto(self)
Definition cp_model.py:3001
_set_objective(self, ObjLinearExprT obj, bool minimize)
Definition cp_model.py:2850
Constraint add_abs_equality(self, LinearExprT target, LinearExprT expr)
Definition cp_model.py:2199
Constraint add_reservoir_constraint_with_active(self, Iterable[LinearExprT] times, Iterable[LinearExprT] level_changes, Iterable[LiteralT] actives, int min_level, int max_level)
Definition cp_model.py:1955
None add_assumption(self, LiteralT lit)
Definition cp_model.py:2970
int get_or_make_index_from_constant(self, IntegralT value)
Definition cp_model.py:2807
IntVar get_int_var_from_proto_index(self, int index)
Definition cp_model.py:2732
int get_or_make_boolean_index(self, LiteralT arg)
Definition cp_model.py:2783
Constraint add_linear_expression_in_domain(self, LinearExprT linear_expr, sorted_interval_list.Domain domain)
Definition cp_model.py:1503
IntervalVar new_optional_fixed_size_interval_var(self, LinearExprT start, IntegralT size, LiteralT is_present, str name)
Definition cp_model.py:2532
Constraint add_exactly_one(self, Iterable[LiteralT] literals)
Definition cp_model.py:2103
Constraint add_linear_constraint(self, LinearExprT linear_expr, IntegralT lb, IntegralT ub)
Definition cp_model.py:1495
Constraint add_min_equality(self, LinearExprT target, Iterable[LinearExprT] exprs)
Definition cp_model.py:2168
IntVar new_bool_var(self, str name)
Definition cp_model.py:1397
int get_or_make_index(self, VariableT arg)
Definition cp_model.py:2768
Constraint add(self, BoundedLinearExpression ct)
Definition cp_model.py:1536
Constraint add_all_different(self, Iterable[LinearExprT] expressions)
Definition cp_model.py:1564
Constraint add_reservoir_constraint(self, Iterable[LinearExprT] times, Iterable[LinearExprT] level_changes, int min_level, int max_level)
Definition cp_model.py:1891
Constraint add_cumulative(self, Iterable[IntervalVar] intervals, Iterable[LinearExprT] demands, LinearExprT capacity)
Definition cp_model.py:2673
pd.Series new_fixed_size_interval_var_series(self, str name, pd.Index index, Union[LinearExprT, pd.Series] starts, Union[IntegralT, pd.Series] sizes)
Definition cp_model.py:2377
Constraint add_multiple_circuit(self, Sequence[ArcT] arcs)
Definition cp_model.py:1651
cp_model_pb2.LinearExpressionProto parse_linear_expression(self, LinearExprT linear_expr, bool negate=False)
Definition cp_model.py:2825
cp_model_pb2.CpModelProto __model
Definition cp_model.py:1343
None add_hint(self, IntVar var, int value)
Definition cp_model.py:2961
Constraint add_at_least_one(self, Iterable[LiteralT] literals)
Definition cp_model.py:2075
cp_model_pb2.IntegerVariableProto var_index_to_var_proto(self, int var_index)
Definition cp_model.py:2817
IntervalVar new_interval_var(self, LinearExprT start, LinearExprT size, LinearExprT end, str name)
Definition cp_model.py:2258
int get_interval_index(self, IntervalVar arg)
Definition cp_model.py:2802
Constraint add_multiplication_equality(self, LinearExprT target, *Union[Iterable[LinearExprT], LinearExprT] expressions)
Definition cp_model.py:2241
Constraint add_inverse(self, Sequence[VariableT] variables, Sequence[VariableT] inverse_variables)
Definition cp_model.py:1852
None assert_is_boolean_variable(self, LiteralT x)
Definition cp_model.py:2984
Constraint add_allowed_assignments(self, Sequence[VariableT] variables, Iterable[Sequence[IntegralT]] tuples_list)
Definition cp_model.py:1692
Constraint add_bool_and(self, Iterable[LiteralT] literals)
Definition cp_model.py:2121
Constraint add_forbidden_assignments(self, Sequence[VariableT] variables, Iterable[Sequence[IntegralT]] tuples_list)
Definition cp_model.py:1739
Constraint add_max_equality(self, LinearExprT target, Iterable[LinearExprT] exprs)
Definition cp_model.py:2180
Constraint add_bool_and(self, *LiteralT literals)
Definition cp_model.py:2124
pd.Series new_optional_fixed_size_interval_var_series(self, str name, pd.Index index, Union[LinearExprT, pd.Series] starts, Union[IntegralT, pd.Series] sizes, Union[LiteralT, pd.Series] are_present)
Definition cp_model.py:2573
Constraint add_modulo_equality(self, LinearExprT target, LinearExprT expr, LinearExprT mod)
Definition cp_model.py:2210
IntervalVar new_fixed_size_interval_var(self, LinearExprT start, IntegralT size, str name)
Definition cp_model.py:2347
None add_decision_strategy(self, Sequence[IntVar] variables, cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy var_strategy, cp_model_pb2.DecisionStrategyProto.DomainReductionStrategy domain_strategy)
Definition cp_model.py:2912
pd.Series new_optional_interval_var_series(self, str name, pd.Index index, Union[LinearExprT, pd.Series] starts, Union[LinearExprT, pd.Series] sizes, Union[LinearExprT, pd.Series] ends, Union[LiteralT, pd.Series] are_present)
Definition cp_model.py:2475
Constraint add_implication(self, LiteralT a, LiteralT b)
Definition cp_model.py:2048
Constraint add(self, Union[bool, np.bool_] ct)
Definition cp_model.py:1539
Constraint add_circuit(self, Sequence[ArcT] arcs)
Definition cp_model.py:1616
Constraint add_division_equality(self, LinearExprT target, LinearExprT num, LinearExprT denom)
Definition cp_model.py:2190
int value(self, LinearExprT expression)
Definition cp_model.py:3487
cp_model_pb2.CpSolverResponse response_proto(self)
Definition cp_model.py:3613
cp_model_pb2.CpSolverResponse response_proto(self)
Definition cp_model.py:3288
cp_model_pb2.CpSolverStatus solve(self, CpModel model, Optional["CpSolverSolutionCallback"] solution_callback=None)
Definition cp_model.py:3168
pd.Series BooleanValues(self, _IndexOrSeries variables)
Definition cp_model.py:3333
cp_model_pb2.CpSolverStatus SolveWithSolutionCallback(self, CpModel model, "CpSolverSolutionCallback" callback)
Definition cp_model.py:3387
cp_model_pb2.CpSolverStatus SearchForAllSolutions(self, CpModel model, "CpSolverSolutionCallback" callback)
Definition cp_model.py:3398
int Value(self, LinearExprT expression)
Definition cp_model.py:3376
pd.Series Values(self, _IndexOrSeries variables)
Definition cp_model.py:3379
cp_model_pb2.CpSolverStatus Solve(self, CpModel model, Optional["CpSolverSolutionCallback"] solution_callback=None)
Definition cp_model.py:3358
pd.Series boolean_values(self, _IndexOrSeries variables)
Definition cp_model.py:3231
int value(self, LinearExprT expression)
Definition cp_model.py:3202
bool boolean_value(self, LiteralT literal)
Definition cp_model.py:3227
cp_model_pb2.CpSolverResponse _solution(self)
Definition cp_model.py:3318
Optional[swig_helper.SolveWrapper] __solve_wrapper
Definition cp_model.py:3161
Optional[cp_model_pb2.CpSolverResponse] __solution
Definition cp_model.py:3155
bool BooleanValue(self, LiteralT literal)
Definition cp_model.py:3330
cp_model_pb2.CpSolverResponse ResponseProto(self)
Definition cp_model.py:3348
str StatusName(self, Optional[Any] status=None)
Definition cp_model.py:3364
Optional[Callable[[str], None]] log_callback
Definition cp_model.py:3159
Optional[Callable[[float], None]] best_bound_callback
Definition cp_model.py:3160
str status_name(self, Optional[Any] status=None)
Definition cp_model.py:3300
pd.Series values(self, _IndexOrSeries variables)
Definition cp_model.py:3206
Sequence[int] sufficient_assumptions_for_infeasibility(self)
Definition cp_model.py:3296
Sequence[int] SufficientAssumptionsForInfeasibility(self)
Definition cp_model.py:3370
cp_model_pb2.IntegerVariableProto __var
Definition cp_model.py:854
"_NotBooleanVariable" __invert__(self)
Definition cp_model.py:930
cp_model_pb2.IntegerVariableProto proto(self)
Definition cp_model.py:881
cp_model_pb2.IntegerVariableProto Proto(self)
Definition cp_model.py:941
bool is_equal_to(self, Any other)
Definition cp_model.py:885
Optional[_NotBooleanVariable] __negation
Definition cp_model.py:855
None __init__(self, cp_model_pb2.CpModelProto model, Union[int, sorted_interval_list.Domain] domain, Optional[str] name)
Definition cp_model.py:851
"_NotBooleanVariable" negated(self)
Definition cp_model.py:912
cp_model_pb2.IntervalConstraintProto proto(self)
Definition cp_model.py:1243
None __init__(self, cp_model_pb2.CpModelProto model, Union[cp_model_pb2.LinearExpressionProto, int] start, Optional[cp_model_pb2.LinearExpressionProto] size, Optional[cp_model_pb2.LinearExpressionProto] end, Optional[int] is_present_index, Optional[str] name)
Definition cp_model.py:1200
cp_model_pb2.CpModelProto __model
Definition cp_model.py:1201
cp_model_pb2.ConstraintProto __ct
Definition cp_model.py:1203
cp_model_pb2.CpModelProto __ct
Definition cp_model.py:1222
cp_model_pb2.IntervalConstraintProto Proto(self)
Definition cp_model.py:1297
Tuple[Dict["IntVar", float], float, bool] get_float_var_value_map(self)
Definition cp_model.py:384
term(cls, expression, coefficient)
Definition cp_model.py:301
"LinearExpr" __add__(self, "LinearExpr" arg)
Definition cp_model.py:439
LinearExprT rebuild_from_linear_expression_proto(cls, cp_model_pb2.CpModelProto model, cp_model_pb2.LinearExpressionProto proto)
Definition cp_model.py:320
Tuple[Dict["IntVar", int], int] get_integer_var_value_map(self)
Definition cp_model.py:344
"LinearExpr" __sub__(self, "LinearExpr" arg)
Definition cp_model.py:459
BoundedLinearExprT __eq__(self, LinearExprT arg)
Definition cp_model.py:565
Union["LinearExpr", NumberT] __mul__(self, NumberT arg)
Definition cp_model.py:486
LinearExprT sum(cls, Sequence[LinearExprT] expressions)
Definition cp_model.py:252
weighted_sum(cls, expressions, coefficients)
Definition cp_model.py:275
LinearExprT Sum(cls, Sequence[LinearExprT] expressions)
Definition cp_model.py:629
bool is_empty_or_all_null(cls, Sequence[NumberT] coefficients)
Definition cp_model.py:309
"BoundedLinearExpression" __ge__(self, LinearExprT arg)
Definition cp_model.py:576
"BoundedLinearExpression" __lt__(self, LinearExprT arg)
Definition cp_model.py:590
Union["LinearExpr", IntegralT] __mul__(self, IntegralT arg)
Definition cp_model.py:483
LinearExprT weighted_sum(cls, Sequence[LinearExprT] expressions, Sequence[IntegralT] coefficients)
Definition cp_model.py:264
"LinearExpr" __rsub__(self, "LinearExpr" arg)
Definition cp_model.py:474
"LinearExpr" __radd__(self, "LinearExpr" arg)
Definition cp_model.py:450
LinearExprT Term(cls, LinearExprT expressions, IntegralT coefficients)
Definition cp_model.py:660
Union["LinearExpr", IntegralT] __rmul__(self, IntegralT arg)
Definition cp_model.py:497
ObjLinearExprT weighted_sum(cls, Sequence[ObjLinearExprT] expressions, Sequence[NumberT] coefficients)
Definition cp_model.py:272
LinearExprT term(cls, LinearExprT expressions, IntegralT coefficients)
Definition cp_model.py:290
"BoundedLinearExpression" __gt__(self, LinearExprT arg)
Definition cp_model.py:599
"LinearExpr" __add__(self, NumberT arg)
Definition cp_model.py:442
ObjLinearExprT term(cls, ObjLinearExprT expressions, NumberT coefficients)
Definition cp_model.py:298
"BoundedLinearExpression" __le__(self, LinearExprT arg)
Definition cp_model.py:583
LinearExprT WeightedSum(cls, Sequence[LinearExprT] expressions, Sequence[IntegralT] coefficients)
Definition cp_model.py:639
BoundedLinearExprT __ne__(self, LinearExprT arg)
Definition cp_model.py:608
None __init__(self, Sequence[IntVar] variables)
Definition cp_model.py:3680
None __init__(self, expr, coeff)
Definition cp_model.py:704
None __init__(self, expressions, constant=0)
Definition cp_model.py:732
None __init__(self, left, right)
Definition cp_model.py:681
None __init__(self, expressions, coefficients, constant=0)
Definition cp_model.py:769
Union[Iterable[LiteralT], LiteralT] expand_generator_or_tuple(Union[Tuple[LiteralT,...], Iterable[LiteralT]] args)
Definition cp_model.py:3070
pd.Index _get_index(_IndexOrSeries obj)
Definition cp_model.py:3704
pd.Series _convert_to_linear_expr_series_and_validate_index(Union[LinearExprT, pd.Series] value_or_series, pd.Index index)
Definition cp_model.py:3761
pd.Series _convert_to_integral_series_and_validate_index(Union[IntegralT, pd.Series] value_or_series, pd.Index index)
Definition cp_model.py:3733
str short_expr_name(cp_model_pb2.CpModelProto model, cp_model_pb2.LinearExpressionProto e)
Definition cp_model.py:196
bool evaluate_boolean_expression(LiteralT literal, cp_model_pb2.CpSolverResponse solution)
Definition cp_model.py:3129
pd.Series _convert_to_literal_series_and_validate_index(Union[LiteralT, pd.Series] value_or_series, pd.Index index)
Definition cp_model.py:3789
str short_name(cp_model_pb2.CpModelProto model, int i)
Definition cp_model.py:181
pd.Series _attribute_series(*, Callable[[IntVar], IntegralT] func, _IndexOrSeries values)
Definition cp_model.py:3715
str display_bounds(Sequence[int] bounds)
Definition cp_model.py:168
bool object_is_a_false_literal(LiteralT literal)
Definition cp_model.py:1320
int evaluate_linear_expr(LinearExprT expression, cp_model_pb2.CpSolverResponse solution)
Definition cp_model.py:3091
bool object_is_a_true_literal(LiteralT literal)
Definition cp_model.py:1307