ortools.sat.python.cp_model

Methods for building and solving CP-SAT models.

The following two sections describe the main methods for building and solving CP-SAT models.

  • .CpModel">CpModel: Methods for creating models, including variables and constraints.
  • .CpSolver">CPSolver: Methods for solving a model and evaluating solutions.

The following methods implement callbacks that the solver calls each time it finds a new solution.

Additional methods for solving CP-SAT models:

  • .Constraint">Constraint: A few utility methods for modifying constraints created by CpModel.
  • .LinearExpr">LinearExpr: Methods for creating constraints and the objective from large arrays of coefficients.

Other methods and functions listed are primarily used for developing OR-Tools, rather than for solving specific optimization problems.

   1# Copyright 2010-2025 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 threading
  49import time
  50from typing import (
  51    Any,
  52    Callable,
  53    Dict,
  54    Iterable,
  55    Optional,
  56    Sequence,
  57    Tuple,
  58    Union,
  59    cast,
  60    overload,
  61)
  62import warnings
  63
  64import numpy as np
  65import pandas as pd
  66
  67from ortools.sat import cp_model_pb2
  68from ortools.sat import sat_parameters_pb2
  69from ortools.sat.python import cp_model_helper as cmh
  70from ortools.sat.python import cp_model_numbers as cmn
  71from ortools.util.python import sorted_interval_list
  72
  73# Import external types.
  74Domain = sorted_interval_list.Domain
  75BoundedLinearExpression = cmh.BoundedLinearExpression
  76FlatFloatExpr = cmh.FlatFloatExpr
  77FlatIntExpr = cmh.FlatIntExpr
  78LinearExpr = cmh.LinearExpr
  79NotBooleanVariable = cmh.NotBooleanVariable
  80
  81
  82# The classes below allow linear expressions to be expressed naturally with the
  83# usual arithmetic operators + - * / and with constant numbers, which makes the
  84# python API very intuitive. See../ samples/*.py for examples.
  85
  86INT_MIN = -(2**63)  # hardcoded to be platform independent.
  87INT_MAX = 2**63 - 1
  88INT32_MIN = -(2**31)
  89INT32_MAX = 2**31 - 1
  90
  91# CpSolver status (exported to avoid importing cp_model_cp2).
  92UNKNOWN = cp_model_pb2.UNKNOWN
  93MODEL_INVALID = cp_model_pb2.MODEL_INVALID
  94FEASIBLE = cp_model_pb2.FEASIBLE
  95INFEASIBLE = cp_model_pb2.INFEASIBLE
  96OPTIMAL = cp_model_pb2.OPTIMAL
  97
  98# Variable selection strategy
  99CHOOSE_FIRST = cp_model_pb2.DecisionStrategyProto.CHOOSE_FIRST
 100CHOOSE_LOWEST_MIN = cp_model_pb2.DecisionStrategyProto.CHOOSE_LOWEST_MIN
 101CHOOSE_HIGHEST_MAX = cp_model_pb2.DecisionStrategyProto.CHOOSE_HIGHEST_MAX
 102CHOOSE_MIN_DOMAIN_SIZE = cp_model_pb2.DecisionStrategyProto.CHOOSE_MIN_DOMAIN_SIZE
 103CHOOSE_MAX_DOMAIN_SIZE = cp_model_pb2.DecisionStrategyProto.CHOOSE_MAX_DOMAIN_SIZE
 104
 105# Domain reduction strategy
 106SELECT_MIN_VALUE = cp_model_pb2.DecisionStrategyProto.SELECT_MIN_VALUE
 107SELECT_MAX_VALUE = cp_model_pb2.DecisionStrategyProto.SELECT_MAX_VALUE
 108SELECT_LOWER_HALF = cp_model_pb2.DecisionStrategyProto.SELECT_LOWER_HALF
 109SELECT_UPPER_HALF = cp_model_pb2.DecisionStrategyProto.SELECT_UPPER_HALF
 110SELECT_MEDIAN_VALUE = cp_model_pb2.DecisionStrategyProto.SELECT_MEDIAN_VALUE
 111SELECT_RANDOM_HALF = cp_model_pb2.DecisionStrategyProto.SELECT_RANDOM_HALF
 112
 113# Search branching
 114AUTOMATIC_SEARCH = sat_parameters_pb2.SatParameters.AUTOMATIC_SEARCH
 115FIXED_SEARCH = sat_parameters_pb2.SatParameters.FIXED_SEARCH
 116PORTFOLIO_SEARCH = sat_parameters_pb2.SatParameters.PORTFOLIO_SEARCH
 117LP_SEARCH = sat_parameters_pb2.SatParameters.LP_SEARCH
 118PSEUDO_COST_SEARCH = sat_parameters_pb2.SatParameters.PSEUDO_COST_SEARCH
 119PORTFOLIO_WITH_QUICK_RESTART_SEARCH = (
 120    sat_parameters_pb2.SatParameters.PORTFOLIO_WITH_QUICK_RESTART_SEARCH
 121)
 122HINT_SEARCH = sat_parameters_pb2.SatParameters.HINT_SEARCH
 123PARTIAL_FIXED_SEARCH = sat_parameters_pb2.SatParameters.PARTIAL_FIXED_SEARCH
 124RANDOMIZED_SEARCH = sat_parameters_pb2.SatParameters.RANDOMIZED_SEARCH
 125
 126# Type aliases
 127IntegralT = Union[int, np.int8, np.uint8, np.int32, np.uint32, np.int64, np.uint64]
 128IntegralTypes = (
 129    int,
 130    np.int8,
 131    np.uint8,
 132    np.int32,
 133    np.uint32,
 134    np.int64,
 135    np.uint64,
 136)
 137NumberT = Union[
 138    int,
 139    float,
 140    np.int8,
 141    np.uint8,
 142    np.int32,
 143    np.uint32,
 144    np.int64,
 145    np.uint64,
 146    np.double,
 147]
 148NumberTypes = (
 149    int,
 150    float,
 151    np.int8,
 152    np.uint8,
 153    np.int32,
 154    np.uint32,
 155    np.int64,
 156    np.uint64,
 157    np.double,
 158)
 159
 160LiteralT = Union[cmh.Literal, IntegralT, bool]
 161BoolVarT = cmh.Literal
 162VariableT = Union["IntVar", IntegralT]
 163
 164# We need to add 'IntVar' for pytype.
 165LinearExprT = Union[LinearExpr, "IntVar", IntegralT]
 166ObjLinearExprT = Union[LinearExpr, NumberT]
 167
 168ArcT = Tuple[IntegralT, IntegralT, LiteralT]
 169_IndexOrSeries = Union[pd.Index, pd.Series]
 170
 171
 172def display_bounds(bounds: Sequence[int]) -> str:
 173    """Displays a flattened list of intervals."""
 174    out = ""
 175    for i in range(0, len(bounds), 2):
 176        if i != 0:
 177            out += ", "
 178        if bounds[i] == bounds[i + 1]:
 179            out += str(bounds[i])
 180        else:
 181            out += str(bounds[i]) + ".." + str(bounds[i + 1])
 182    return out
 183
 184
 185def short_name(model: cp_model_pb2.CpModelProto, i: int) -> str:
 186    """Returns a short name of an integer variable, or its negation."""
 187    if i < 0:
 188        return f"not({short_name(model, -i - 1)})"
 189    v = model.variables[i]
 190    if v.name:
 191        return v.name
 192    elif len(v.domain) == 2 and v.domain[0] == v.domain[1]:
 193        return str(v.domain[0])
 194    else:
 195        return f"[{display_bounds(v.domain)}]"
 196
 197
 198def short_expr_name(
 199    model: cp_model_pb2.CpModelProto, e: cp_model_pb2.LinearExpressionProto
 200) -> str:
 201    """Pretty-print LinearExpressionProto instances."""
 202    if not e.vars:
 203        return str(e.offset)
 204    if len(e.vars) == 1:
 205        var_name = short_name(model, e.vars[0])
 206        coeff = e.coeffs[0]
 207        result = ""
 208        if coeff == 1:
 209            result = var_name
 210        elif coeff == -1:
 211            result = f"-{var_name}"
 212        elif coeff != 0:
 213            result = f"{coeff} * {var_name}"
 214        if e.offset > 0:
 215            result = f"{result} + {e.offset}"
 216        elif e.offset < 0:
 217            result = f"{result} - {-e.offset}"
 218        return result
 219    # TODO(user): Support more than affine expressions.
 220    return str(e)
 221
 222
 223class IntVar(cmh.BaseIntVar):
 224    """An integer variable.
 225
 226    An IntVar is an object that can take on any integer value within defined
 227    ranges. Variables appear in constraint like:
 228
 229        x + y >= 5
 230        AllDifferent([x, y, z])
 231
 232    Solving a model is equivalent to finding, for each variable, a single value
 233    from the set of initial values (called the initial domain), such that the
 234    model is feasible, or optimal if you provided an objective function.
 235    """
 236
 237    def __init__(
 238        self,
 239        model: cp_model_pb2.CpModelProto,
 240        domain: Union[int, sorted_interval_list.Domain],
 241        is_boolean: bool,
 242        name: Optional[str],
 243    ) -> None:
 244        """See CpModel.new_int_var below."""
 245        self.__var: cp_model_pb2.IntegerVariableProto
 246        # Python do not support multiple __init__ methods.
 247        # This method is only called from the CpModel class.
 248        # We hack the parameter to support the two cases:
 249        # case 1:
 250        #     model is a CpModelProto, domain is a Domain, and name is a string.
 251        # case 2:
 252        #     model is a CpModelProto, domain is an index (int), and name is None.
 253        if isinstance(domain, IntegralTypes) and name is None:
 254            cmh.BaseIntVar.__init__(self, int(domain), is_boolean)
 255            self.__var = model.variables[domain]
 256        else:
 257            cmh.BaseIntVar.__init__(self, len(model.variables), is_boolean)
 258            self.__var = model.variables.add()
 259            self.__var.domain.extend(
 260                cast(sorted_interval_list.Domain, domain).flattened_intervals()
 261            )
 262            if name is not None:
 263                self.__var.name = name
 264
 265    @property
 266    def proto(self) -> cp_model_pb2.IntegerVariableProto:
 267        """Returns the variable protobuf."""
 268        return self.__var
 269
 270    def is_equal_to(self, other: Any) -> bool:
 271        """Returns true if self == other in the python sense."""
 272        if not isinstance(other, IntVar):
 273            return False
 274        return self.index == other.index
 275
 276    def __str__(self) -> str:
 277        if not self.__var.name:
 278            if (
 279                len(self.__var.domain) == 2
 280                and self.__var.domain[0] == self.__var.domain[1]
 281            ):
 282                # Special case for constants.
 283                return str(self.__var.domain[0])
 284            elif self.is_boolean:
 285                return f"BooleanVar({self.__index})"
 286            else:
 287                return f"IntVar({self.__index})"
 288        else:
 289            return self.__var.name
 290
 291    def __repr__(self) -> str:
 292        return f"{self}({display_bounds(self.__var.domain)})"
 293
 294    @property
 295    def name(self) -> str:
 296        if not self.__var or not self.__var.name:
 297            return ""
 298        return self.__var.name
 299
 300    # Pre PEP8 compatibility.
 301    # pylint: disable=invalid-name
 302    def Name(self) -> str:
 303        return self.name
 304
 305    def Proto(self) -> cp_model_pb2.IntegerVariableProto:
 306        return self.proto
 307
 308    # pylint: enable=invalid-name
 309
 310
 311class Constraint:
 312    """Base class for constraints.
 313
 314    Constraints are built by the CpModel through the add<XXX> methods.
 315    Once created by the CpModel class, they are automatically added to the model.
 316    The purpose of this class is to allow specification of enforcement literals
 317    for this constraint.
 318
 319        b = model.new_bool_var('b')
 320        x = model.new_int_var(0, 10, 'x')
 321        y = model.new_int_var(0, 10, 'y')
 322
 323        model.add(x + 2 * y == 5).only_enforce_if(b.negated())
 324    """
 325
 326    def __init__(
 327        self,
 328        cp_model: "CpModel",
 329    ) -> None:
 330        self.__index: int = len(cp_model.proto.constraints)
 331        self.__cp_model: "CpModel" = cp_model
 332        self.__constraint: cp_model_pb2.ConstraintProto = (
 333            cp_model.proto.constraints.add()
 334        )
 335
 336    @overload
 337    def only_enforce_if(self, boolvar: Iterable[LiteralT]) -> "Constraint": ...
 338
 339    @overload
 340    def only_enforce_if(self, *boolvar: LiteralT) -> "Constraint": ...
 341
 342    def only_enforce_if(self, *boolvar) -> "Constraint":
 343        """Adds an enforcement literal to the constraint.
 344
 345        This method adds one or more literals (that is, a boolean variable or its
 346        negation) as enforcement literals. The conjunction of all these literals
 347        determines whether the constraint is active or not. It acts as an
 348        implication, so if the conjunction is true, it implies that the constraint
 349        must be enforced. If it is false, then the constraint is ignored.
 350
 351        BoolOr, BoolAnd, and linear constraints all support enforcement literals.
 352
 353        Args:
 354          *boolvar: One or more Boolean literals.
 355
 356        Returns:
 357          self.
 358        """
 359        for lit in expand_generator_or_tuple(boolvar):
 360            if (cmn.is_boolean(lit) and lit) or (
 361                isinstance(lit, IntegralTypes) and lit == 1
 362            ):
 363                # Always true. Do nothing.
 364                pass
 365            elif (cmn.is_boolean(lit) and not lit) or (
 366                isinstance(lit, IntegralTypes) and lit == 0
 367            ):
 368                self.__constraint.enforcement_literal.append(
 369                    self.__cp_model.new_constant(0).index
 370                )
 371            else:
 372                self.__constraint.enforcement_literal.append(
 373                    cast(cmh.Literal, lit).index
 374                )
 375        return self
 376
 377    def with_name(self, name: str) -> "Constraint":
 378        """Sets the name of the constraint."""
 379        if name:
 380            self.__constraint.name = name
 381        else:
 382            self.__constraint.ClearField("name")
 383        return self
 384
 385    @property
 386    def name(self) -> str:
 387        """Returns the name of the constraint."""
 388        if not self.__constraint or not self.__constraint.name:
 389            return ""
 390        return self.__constraint.name
 391
 392    @property
 393    def index(self) -> int:
 394        """Returns the index of the constraint in the model."""
 395        return self.__index
 396
 397    @property
 398    def proto(self) -> cp_model_pb2.ConstraintProto:
 399        """Returns the constraint protobuf."""
 400        return self.__constraint
 401
 402    # Pre PEP8 compatibility.
 403    # pylint: disable=invalid-name
 404    OnlyEnforceIf = only_enforce_if
 405    WithName = with_name
 406
 407    def Name(self) -> str:
 408        return self.name
 409
 410    def Index(self) -> int:
 411        return self.index
 412
 413    def Proto(self) -> cp_model_pb2.ConstraintProto:
 414        return self.proto
 415
 416    # pylint: enable=invalid-name
 417
 418
 419class VariableList:
 420    """Stores all integer variables of the model."""
 421
 422    def __init__(self) -> None:
 423        self.__var_list: list[IntVar] = []
 424
 425    def append(self, var: IntVar) -> None:
 426        assert var.index == len(self.__var_list)
 427        self.__var_list.append(var)
 428
 429    def get(self, index: int) -> IntVar:
 430        if index < 0 or index >= len(self.__var_list):
 431            raise ValueError("Index out of bounds.")
 432        return self.__var_list[index]
 433
 434    def rebuild_expr(
 435        self,
 436        proto: cp_model_pb2.LinearExpressionProto,
 437    ) -> LinearExprT:
 438        """Recreate a LinearExpr from a LinearExpressionProto."""
 439        num_elements = len(proto.vars)
 440        if num_elements == 0:
 441            return proto.offset
 442        elif num_elements == 1:
 443            var = self.get(proto.vars[0])
 444            return LinearExpr.affine(
 445                var, proto.coeffs[0], proto.offset
 446            )  # pytype: disable=bad-return-type
 447        else:
 448            variables = []
 449            for var_index in range(len(proto.vars)):
 450                var = self.get(var_index)
 451                variables.append(var)
 452            if proto.offset != 0:
 453                coeffs = []
 454                coeffs.extend(proto.coeffs)
 455                coeffs.append(1)
 456                variables.append(proto.offset)
 457                return LinearExpr.weighted_sum(variables, coeffs)
 458            else:
 459                return LinearExpr.weighted_sum(variables, proto.coeffs)
 460
 461
 462class IntervalVar:
 463    """Represents an Interval variable.
 464
 465    An interval variable is both a constraint and a variable. It is defined by
 466    three integer variables: start, size, and end.
 467
 468    It is a constraint because, internally, it enforces that start + size == end.
 469
 470    It is also a variable as it can appear in specific scheduling constraints:
 471    NoOverlap, NoOverlap2D, Cumulative.
 472
 473    Optionally, an enforcement literal can be added to this constraint, in which
 474    case these scheduling constraints will ignore interval variables with
 475    enforcement literals assigned to false. Conversely, these constraints will
 476    also set these enforcement literals to false if they cannot fit these
 477    intervals into the schedule.
 478
 479    Raises:
 480      ValueError: if start, size, end are not defined, or have the wrong type.
 481    """
 482
 483    def __init__(
 484        self,
 485        model: cp_model_pb2.CpModelProto,
 486        var_list: VariableList,
 487        start: Union[cp_model_pb2.LinearExpressionProto, int],
 488        size: Optional[cp_model_pb2.LinearExpressionProto],
 489        end: Optional[cp_model_pb2.LinearExpressionProto],
 490        is_present_index: Optional[int],
 491        name: Optional[str],
 492    ) -> None:
 493        self.__model: cp_model_pb2.CpModelProto = model
 494        self.__var_list: VariableList = var_list
 495        self.__index: int
 496        self.__ct: cp_model_pb2.ConstraintProto
 497        # As with the IntVar::__init__ method, we hack the __init__ method to
 498        # support two use cases:
 499        #   case 1: called when creating a new interval variable.
 500        #      {start|size|end} are linear expressions, is_present_index is either
 501        #      None or the index of a Boolean literal. name is a string
 502        #   case 2: called when querying an existing interval variable.
 503        #      start_index is an int, all parameters after are None.
 504        if isinstance(start, int):
 505            if size is not None:
 506                raise ValueError("size should be None")
 507            if end is not None:
 508                raise ValueError("end should be None")
 509            if is_present_index is not None:
 510                raise ValueError("is_present_index should be None")
 511            self.__index = cast(int, start)
 512            self.__ct = model.constraints[self.__index]
 513        else:
 514            self.__index = len(model.constraints)
 515            self.__ct = self.__model.constraints.add()
 516            if start is None:
 517                raise TypeError("start is not defined")
 518            self.__ct.interval.start.CopyFrom(start)
 519            if size is None:
 520                raise TypeError("size is not defined")
 521            self.__ct.interval.size.CopyFrom(size)
 522            if end is None:
 523                raise TypeError("end is not defined")
 524            self.__ct.interval.end.CopyFrom(end)
 525            if is_present_index is not None:
 526                self.__ct.enforcement_literal.append(is_present_index)
 527            if name:
 528                self.__ct.name = name
 529
 530    @property
 531    def index(self) -> int:
 532        """Returns the index of the interval constraint in the model."""
 533        return self.__index
 534
 535    @property
 536    def proto(self) -> cp_model_pb2.IntervalConstraintProto:
 537        """Returns the interval protobuf."""
 538        return self.__ct.interval
 539
 540    def __str__(self):
 541        return self.__ct.name
 542
 543    def __repr__(self):
 544        interval = self.__ct.interval
 545        if self.__ct.enforcement_literal:
 546            return (
 547                f"{self.__ct.name}(start ="
 548                f" {short_expr_name(self.__model, interval.start)}, size ="
 549                f" {short_expr_name(self.__model, interval.size)}, end ="
 550                f" {short_expr_name(self.__model, interval.end)}, is_present ="
 551                f" {short_name(self.__model, self.__ct.enforcement_literal[0])})"
 552            )
 553        else:
 554            return (
 555                f"{self.__ct.name}(start ="
 556                f" {short_expr_name(self.__model, interval.start)}, size ="
 557                f" {short_expr_name(self.__model, interval.size)}, end ="
 558                f" {short_expr_name(self.__model, interval.end)})"
 559            )
 560
 561    @property
 562    def name(self) -> str:
 563        if not self.__ct or not self.__ct.name:
 564            return ""
 565        return self.__ct.name
 566
 567    def start_expr(self) -> LinearExprT:
 568        return self.__var_list.rebuild_expr(self.__ct.interval.start)
 569
 570    def size_expr(self) -> LinearExprT:
 571        return self.__var_list.rebuild_expr(self.__ct.interval.size)
 572
 573    def end_expr(self) -> LinearExprT:
 574        return self.__var_list.rebuild_expr(self.__ct.interval.end)
 575
 576    # Pre PEP8 compatibility.
 577    # pylint: disable=invalid-name
 578    def Name(self) -> str:
 579        return self.name
 580
 581    def Index(self) -> int:
 582        return self.index
 583
 584    def Proto(self) -> cp_model_pb2.IntervalConstraintProto:
 585        return self.proto
 586
 587    StartExpr = start_expr
 588    SizeExpr = size_expr
 589    EndExpr = end_expr
 590
 591    # pylint: enable=invalid-name
 592
 593
 594def object_is_a_true_literal(literal: LiteralT) -> bool:
 595    """Checks if literal is either True, or a Boolean literals fixed to True."""
 596    if isinstance(literal, IntVar):
 597        proto = literal.proto
 598        return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1
 599    if isinstance(literal, cmh.NotBooleanVariable):
 600        proto = literal.negated().proto
 601        return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0
 602    if isinstance(literal, IntegralTypes):
 603        return int(literal) == 1
 604    return False
 605
 606
 607def object_is_a_false_literal(literal: LiteralT) -> bool:
 608    """Checks if literal is either False, or a Boolean literals fixed to False."""
 609    if isinstance(literal, IntVar):
 610        proto = literal.proto
 611        return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0
 612    if isinstance(literal, cmh.NotBooleanVariable):
 613        proto = literal.negated().proto
 614        return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1
 615    if isinstance(literal, IntegralTypes):
 616        return int(literal) == 0
 617    return False
 618
 619
 620class CpModel:
 621    """Methods for building a CP model.
 622
 623    Methods beginning with:
 624
 625    * ```New``` create integer, boolean, or interval variables.
 626    * ```add``` create new constraints and add them to the model.
 627    """
 628
 629    def __init__(self) -> None:
 630        self.__model: cp_model_pb2.CpModelProto = cp_model_pb2.CpModelProto()
 631        self.__constant_map: Dict[IntegralT, int] = {}
 632        self.__var_list: VariableList = VariableList()
 633
 634    # Naming.
 635    @property
 636    def name(self) -> str:
 637        """Returns the name of the model."""
 638        if not self.__model or not self.__model.name:
 639            return ""
 640        return self.__model.name
 641
 642    @name.setter
 643    def name(self, name: str):
 644        """Sets the name of the model."""
 645        self.__model.name = name
 646
 647    # Integer variable.
 648
 649    def _append_int_var(self, var: IntVar) -> IntVar:
 650        """Appends an integer variable to the list of variables."""
 651        self.__var_list.append(var)
 652        return var
 653
 654    def _get_int_var(self, index: int) -> IntVar:
 655        return self.__var_list.get(index)
 656
 657    def rebuild_from_linear_expression_proto(
 658        self,
 659        proto: cp_model_pb2.LinearExpressionProto,
 660    ) -> LinearExpr:
 661        return self.__var_list.rebuild_expr(proto)
 662
 663    def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar:
 664        """Create an integer variable with domain [lb, ub].
 665
 666        The CP-SAT solver is limited to integer variables. If you have fractional
 667        values, scale them up so that they become integers; if you have strings,
 668        encode them as integers.
 669
 670        Args:
 671          lb: Lower bound for the variable.
 672          ub: Upper bound for the variable.
 673          name: The name of the variable.
 674
 675        Returns:
 676          a variable whose domain is [lb, ub].
 677        """
 678        domain_is_boolean = lb >= 0 and ub <= 1
 679        return self._append_int_var(
 680            IntVar(
 681                self.__model,
 682                sorted_interval_list.Domain(lb, ub),
 683                domain_is_boolean,
 684                name,
 685            )
 686        )
 687
 688    def new_int_var_from_domain(
 689        self, domain: sorted_interval_list.Domain, name: str
 690    ) -> IntVar:
 691        """Create an integer variable from a domain.
 692
 693        A domain is a set of integers specified by a collection of intervals.
 694        For example, `model.new_int_var_from_domain(cp_model.
 695             Domain.from_intervals([[1, 2], [4, 6]]), 'x')`
 696
 697        Args:
 698          domain: An instance of the Domain class.
 699          name: The name of the variable.
 700
 701        Returns:
 702            a variable whose domain is the given domain.
 703        """
 704        domain_is_boolean = domain.min() >= 0 and domain.max() <= 1
 705        return self._append_int_var(
 706            IntVar(self.__model, domain, domain_is_boolean, name)
 707        )
 708
 709    def new_bool_var(self, name: str) -> IntVar:
 710        """Creates a 0-1 variable with the given name."""
 711        return self._append_int_var(
 712            IntVar(self.__model, sorted_interval_list.Domain(0, 1), True, name)
 713        )
 714
 715    def new_constant(self, value: IntegralT) -> IntVar:
 716        """Declares a constant integer."""
 717        index: int = self.get_or_make_index_from_constant(value)
 718        return self._get_int_var(index)
 719
 720    def new_int_var_series(
 721        self,
 722        name: str,
 723        index: pd.Index,
 724        lower_bounds: Union[IntegralT, pd.Series],
 725        upper_bounds: Union[IntegralT, pd.Series],
 726    ) -> pd.Series:
 727        """Creates a series of (scalar-valued) variables with the given name.
 728
 729        Args:
 730          name (str): Required. The name of the variable set.
 731          index (pd.Index): Required. The index to use for the variable set.
 732          lower_bounds (Union[int, pd.Series]): A lower bound for variables in the
 733            set. If a `pd.Series` is passed in, it will be based on the
 734            corresponding values of the pd.Series.
 735          upper_bounds (Union[int, pd.Series]): An upper bound for variables in the
 736            set. If a `pd.Series` is passed in, it will be based on the
 737            corresponding values of the pd.Series.
 738
 739        Returns:
 740          pd.Series: The variable set indexed by its corresponding dimensions.
 741
 742        Raises:
 743          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 744          ValueError: if the `name` is not a valid identifier or already exists.
 745          ValueError: if the `lowerbound` is greater than the `upperbound`.
 746          ValueError: if the index of `lower_bound`, or `upper_bound` does not match
 747          the input index.
 748        """
 749        if not isinstance(index, pd.Index):
 750            raise TypeError("Non-index object is used as index")
 751        if not name.isidentifier():
 752            raise ValueError(f"name={name!r} is not a valid identifier")
 753        if (
 754            isinstance(lower_bounds, IntegralTypes)
 755            and isinstance(upper_bounds, IntegralTypes)
 756            and lower_bounds > upper_bounds
 757        ):
 758            raise ValueError(
 759                f"lower_bound={lower_bounds} is greater than"
 760                f" upper_bound={upper_bounds} for variable set={name}"
 761            )
 762
 763        lower_bounds = _convert_to_integral_series_and_validate_index(
 764            lower_bounds, index
 765        )
 766        upper_bounds = _convert_to_integral_series_and_validate_index(
 767            upper_bounds, index
 768        )
 769        return pd.Series(
 770            index=index,
 771            data=[
 772                # pylint: disable=g-complex-comprehension
 773                self._append_int_var(
 774                    IntVar(
 775                        model=self.__model,
 776                        name=f"{name}[{i}]",
 777                        domain=sorted_interval_list.Domain(
 778                            lower_bounds[i], upper_bounds[i]
 779                        ),
 780                        is_boolean=lower_bounds[i] >= 0 and upper_bounds[i] <= 1,
 781                    )
 782                )
 783                for i in index
 784            ],
 785        )
 786
 787    def new_bool_var_series(
 788        self,
 789        name: str,
 790        index: pd.Index,
 791    ) -> pd.Series:
 792        """Creates a series of (scalar-valued) variables with the given name.
 793
 794        Args:
 795          name (str): Required. The name of the variable set.
 796          index (pd.Index): Required. The index to use for the variable set.
 797
 798        Returns:
 799          pd.Series: The variable set indexed by its corresponding dimensions.
 800
 801        Raises:
 802          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 803          ValueError: if the `name` is not a valid identifier or already exists.
 804        """
 805        if not isinstance(index, pd.Index):
 806            raise TypeError("Non-index object is used as index")
 807        if not name.isidentifier():
 808            raise ValueError(f"name={name!r} is not a valid identifier")
 809        return pd.Series(
 810            index=index,
 811            data=[
 812                # pylint: disable=g-complex-comprehension
 813                self._append_int_var(
 814                    IntVar(
 815                        model=self.__model,
 816                        name=f"{name}[{i}]",
 817                        domain=sorted_interval_list.Domain(0, 1),
 818                        is_boolean=True,
 819                    )
 820                )
 821                for i in index
 822            ],
 823        )
 824
 825    # Linear constraints.
 826
 827    def add_linear_constraint(
 828        self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT
 829    ) -> Constraint:
 830        """Adds the constraint: `lb <= linear_expr <= ub`."""
 831        return self.add_linear_expression_in_domain(
 832            linear_expr, sorted_interval_list.Domain(lb, ub)
 833        )
 834
 835    def add_linear_expression_in_domain(
 836        self,
 837        linear_expr: LinearExprT,
 838        domain: sorted_interval_list.Domain,
 839    ) -> Constraint:
 840        """Adds the constraint: `linear_expr` in `domain`."""
 841        if isinstance(linear_expr, LinearExpr):
 842            ble = BoundedLinearExpression(linear_expr, domain)
 843            if not ble.ok:
 844                raise TypeError(
 845                    "Cannot add a linear expression containing floating point"
 846                    f" coefficients or constants: {type(linear_expr).__name__!r}"
 847                )
 848            return self.add(ble)
 849        if isinstance(linear_expr, IntegralTypes):
 850            if not domain.contains(int(linear_expr)):
 851                return self.add_bool_or([])  # Evaluate to false.
 852            else:
 853                return self.add_bool_and([])  # Evaluate to true.
 854        raise TypeError(
 855            "not supported:"
 856            f" CpModel.add_linear_expression_in_domain({type(linear_expr).__name__!r})"
 857        )
 858
 859    def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint:
 860        """Adds a `BoundedLinearExpression` to the model.
 861
 862        Args:
 863          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
 864
 865        Returns:
 866          An instance of the `Constraint` class.
 867
 868        Raises:
 869          TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean.
 870        """
 871        if isinstance(ct, BoundedLinearExpression):
 872            result = Constraint(self)
 873            model_ct = self.__model.constraints[result.index]
 874            for var in ct.vars:
 875                model_ct.linear.vars.append(var.index)
 876            model_ct.linear.coeffs.extend(ct.coeffs)
 877            model_ct.linear.domain.extend(
 878                [
 879                    cmn.capped_subtraction(x, ct.offset)
 880                    for x in ct.bounds.flattened_intervals()
 881                ]
 882            )
 883            return result
 884        if ct and cmn.is_boolean(ct):
 885            return self.add_bool_or([True])
 886        if not ct and cmn.is_boolean(ct):
 887            return self.add_bool_or([])  # Evaluate to false.
 888        raise TypeError(f"not supported: CpModel.add({type(ct).__name__!r})")
 889
 890    # General Integer Constraints.
 891
 892    @overload
 893    def add_all_different(self, expressions: Iterable[LinearExprT]) -> Constraint: ...
 894
 895    @overload
 896    def add_all_different(self, *expressions: LinearExprT) -> Constraint: ...
 897
 898    def add_all_different(self, *expressions):
 899        """Adds AllDifferent(expressions).
 900
 901        This constraint forces all expressions to have different values.
 902
 903        Args:
 904          *expressions: simple expressions of the form a * var + constant.
 905
 906        Returns:
 907          An instance of the `Constraint` class.
 908        """
 909        ct = Constraint(self)
 910        model_ct = self.__model.constraints[ct.index]
 911        expanded = expand_generator_or_tuple(expressions)
 912        model_ct.all_diff.exprs.extend(
 913            self.parse_linear_expression(x) for x in expanded
 914        )
 915        return ct
 916
 917    def add_element(
 918        self,
 919        index: LinearExprT,
 920        expressions: Sequence[LinearExprT],
 921        target: LinearExprT,
 922    ) -> Constraint:
 923        """Adds the element constraint: `expressions[index] == target`.
 924
 925        Args:
 926          index: The index of the selected expression in the array. It must be an
 927            affine expression (a * var + b).
 928          expressions: A list of affine expressions.
 929          target: The expression constrained to be equal to the selected expression.
 930            It must be an affine expression (a * var + b).
 931
 932        Returns:
 933          An instance of the `Constraint` class.
 934        """
 935
 936        if not expressions:
 937            raise ValueError("add_element expects a non-empty expressions array")
 938
 939        if isinstance(index, IntegralTypes):
 940            expression: LinearExprT = list(expressions)[int(index)]
 941            return self.add(expression == target)
 942
 943        ct = Constraint(self)
 944        model_ct = self.__model.constraints[ct.index]
 945        model_ct.element.linear_index.CopyFrom(self.parse_linear_expression(index))
 946        model_ct.element.exprs.extend(
 947            [self.parse_linear_expression(e) for e in expressions]
 948        )
 949        model_ct.element.linear_target.CopyFrom(self.parse_linear_expression(target))
 950        return ct
 951
 952    def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
 953        """Adds Circuit(arcs).
 954
 955        Adds a circuit constraint from a sparse list of arcs that encode the graph.
 956
 957        A circuit is a unique Hamiltonian cycle in a subgraph of the total
 958        graph. In case a node 'i' is not in the cycle, then there must be a
 959        loop arc 'i -> i' associated with a true literal. Otherwise
 960        this constraint will fail.
 961
 962        Args:
 963          arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
 964            literal). The arc is selected in the circuit if the literal is true.
 965            Both source_node and destination_node must be integers between 0 and the
 966            number of nodes - 1.
 967
 968        Returns:
 969          An instance of the `Constraint` class.
 970
 971        Raises:
 972          ValueError: If the list of arcs is empty.
 973        """
 974        if not arcs:
 975            raise ValueError("add_circuit expects a non-empty array of arcs")
 976        ct = Constraint(self)
 977        model_ct = self.__model.constraints[ct.index]
 978        for arc in arcs:
 979            model_ct.circuit.tails.append(arc[0])
 980            model_ct.circuit.heads.append(arc[1])
 981            model_ct.circuit.literals.append(self.get_or_make_boolean_index(arc[2]))
 982        return ct
 983
 984    def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
 985        """Adds a multiple circuit constraint, aka the 'VRP' constraint.
 986
 987        The direct graph where arc #i (from tails[i] to head[i]) is present iff
 988        literals[i] is true must satisfy this set of properties:
 989        - #incoming arcs == 1 except for node 0.
 990        - #outgoing arcs == 1 except for node 0.
 991        - for node zero, #incoming arcs == #outgoing arcs.
 992        - There are no duplicate arcs.
 993        - Self-arcs are allowed except for node 0.
 994        - There is no cycle in this graph, except through node 0.
 995
 996        Args:
 997          arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
 998            literal). The arc is selected in the circuit if the literal is true.
 999            Both source_node and destination_node must be integers between 0 and the
1000            number of nodes - 1.
1001
1002        Returns:
1003          An instance of the `Constraint` class.
1004
1005        Raises:
1006          ValueError: If the list of arcs is empty.
1007        """
1008        if not arcs:
1009            raise ValueError("add_multiple_circuit expects a non-empty array of arcs")
1010        ct = Constraint(self)
1011        model_ct = self.__model.constraints[ct.index]
1012        for arc in arcs:
1013            model_ct.routes.tails.append(arc[0])
1014            model_ct.routes.heads.append(arc[1])
1015            model_ct.routes.literals.append(self.get_or_make_boolean_index(arc[2]))
1016        return ct
1017
1018    def add_allowed_assignments(
1019        self,
1020        expressions: Sequence[LinearExprT],
1021        tuples_list: Iterable[Sequence[IntegralT]],
1022    ) -> Constraint:
1023        """Adds AllowedAssignments(expressions, tuples_list).
1024
1025        An AllowedAssignments constraint is a constraint on an array of affine
1026        expressions, which requires that when all expressions are assigned values,
1027        the
1028        resulting array equals one of the  tuples in `tuple_list`.
1029
1030        Args:
1031          expressions: A list of affine expressions (a * var + b).
1032          tuples_list: A list of admissible tuples. Each tuple must have the same
1033            length as the expressions, and the ith value of a tuple corresponds to
1034            the ith expression.
1035
1036        Returns:
1037          An instance of the `Constraint` class.
1038
1039        Raises:
1040          TypeError: If a tuple does not have the same size as the list of
1041              expressions.
1042          ValueError: If the array of expressions is empty.
1043        """
1044
1045        if not expressions:
1046            raise ValueError(
1047                "add_allowed_assignments expects a non-empty expressions array"
1048            )
1049
1050        ct: Constraint = Constraint(self)
1051        model_ct = self.__model.constraints[ct.index]
1052        model_ct.table.exprs.extend(
1053            [self.parse_linear_expression(e) for e in expressions]
1054        )
1055        arity: int = len(expressions)
1056        for one_tuple in tuples_list:
1057            if len(one_tuple) != arity:
1058                raise TypeError(f"Tuple {one_tuple!r} has the wrong arity")
1059
1060        # duck-typing (no explicit type checks here)
1061        try:
1062            for one_tuple in tuples_list:
1063                model_ct.table.values.extend(one_tuple)
1064        except ValueError as ex:
1065            raise TypeError(
1066                "add_xxx_assignment: Not an integer or does not fit in an int64_t:"
1067                f" {type(ex.args).__name__!r}"
1068            ) from ex
1069
1070        return ct
1071
1072    def add_forbidden_assignments(
1073        self,
1074        expressions: Sequence[LinearExprT],
1075        tuples_list: Iterable[Sequence[IntegralT]],
1076    ) -> Constraint:
1077        """Adds add_forbidden_assignments(expressions, [tuples_list]).
1078
1079        A ForbiddenAssignments constraint is a constraint on an array of affine
1080        expressions where the list of impossible combinations is provided in the
1081        tuples list.
1082
1083        Args:
1084          expressions: A list of affine expressions (a * var + b).
1085          tuples_list: A list of forbidden tuples. Each tuple must have the same
1086            length as the expressions, and the *i*th value of a tuple corresponds to
1087            the *i*th expression.
1088
1089        Returns:
1090          An instance of the `Constraint` class.
1091
1092        Raises:
1093          TypeError: If a tuple does not have the same size as the list of
1094                     expressions.
1095          ValueError: If the array of expressions is empty.
1096        """
1097
1098        if not expressions:
1099            raise ValueError(
1100                "add_forbidden_assignments expects a non-empty expressions array"
1101            )
1102
1103        index: int = len(self.__model.constraints)
1104        ct: Constraint = self.add_allowed_assignments(expressions, tuples_list)
1105        self.__model.constraints[index].table.negated = True
1106        return ct
1107
1108    def add_automaton(
1109        self,
1110        transition_expressions: Sequence[LinearExprT],
1111        starting_state: IntegralT,
1112        final_states: Sequence[IntegralT],
1113        transition_triples: Sequence[Tuple[IntegralT, IntegralT, IntegralT]],
1114    ) -> Constraint:
1115        """Adds an automaton constraint.
1116
1117        An automaton constraint takes a list of affine expressions (a * var + b) (of
1118        size *n*), an initial state, a set of final states, and a set of
1119        transitions. A transition is a triplet (*tail*, *transition*, *head*), where
1120        *tail* and *head* are states, and *transition* is the label of an arc from
1121        *head* to *tail*, corresponding to the value of one expression in the list
1122        of
1123        expressions.
1124
1125        This automaton will be unrolled into a flow with *n* + 1 phases. Each phase
1126        contains the possible states of the automaton. The first state contains the
1127        initial state. The last phase contains the final states.
1128
1129        Between two consecutive phases *i* and *i* + 1, the automaton creates a set
1130        of arcs. For each transition (*tail*, *transition*, *head*), it will add
1131        an arc from the state *tail* of phase *i* and the state *head* of phase
1132        *i* + 1. This arc is labeled by the value *transition* of the expression
1133        `expressions[i]`. That is, this arc can only be selected if `expressions[i]`
1134        is assigned the value *transition*.
1135
1136        A feasible solution of this constraint is an assignment of expressions such
1137        that, starting from the initial state in phase 0, there is a path labeled by
1138        the values of the expressions that ends in one of the final states in the
1139        final phase.
1140
1141        Args:
1142          transition_expressions: A non-empty list of affine expressions (a * var +
1143            b) whose values correspond to the labels of the arcs traversed by the
1144            automaton.
1145          starting_state: The initial state of the automaton.
1146          final_states: A non-empty list of admissible final states.
1147          transition_triples: A list of transitions for the automaton, in the
1148            following format (current_state, variable_value, next_state).
1149
1150        Returns:
1151          An instance of the `Constraint` class.
1152
1153        Raises:
1154          ValueError: if `transition_expressions`, `final_states`, or
1155            `transition_triples` are empty.
1156        """
1157
1158        if not transition_expressions:
1159            raise ValueError(
1160                "add_automaton expects a non-empty transition_expressions array"
1161            )
1162        if not final_states:
1163            raise ValueError("add_automaton expects some final states")
1164
1165        if not transition_triples:
1166            raise ValueError("add_automaton expects some transition triples")
1167
1168        ct = Constraint(self)
1169        model_ct = self.__model.constraints[ct.index]
1170        model_ct.automaton.exprs.extend(
1171            [self.parse_linear_expression(e) for e in transition_expressions]
1172        )
1173        model_ct.automaton.starting_state = starting_state
1174        for v in final_states:
1175            model_ct.automaton.final_states.append(v)
1176        for t in transition_triples:
1177            if len(t) != 3:
1178                raise TypeError(f"Tuple {t!r} has the wrong arity (!= 3)")
1179            model_ct.automaton.transition_tail.append(t[0])
1180            model_ct.automaton.transition_label.append(t[1])
1181            model_ct.automaton.transition_head.append(t[2])
1182        return ct
1183
1184    def add_inverse(
1185        self,
1186        variables: Sequence[VariableT],
1187        inverse_variables: Sequence[VariableT],
1188    ) -> Constraint:
1189        """Adds Inverse(variables, inverse_variables).
1190
1191        An inverse constraint enforces that if `variables[i]` is assigned a value
1192        `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa.
1193
1194        Args:
1195          variables: An array of integer variables.
1196          inverse_variables: An array of integer variables.
1197
1198        Returns:
1199          An instance of the `Constraint` class.
1200
1201        Raises:
1202          TypeError: if variables and inverse_variables have different lengths, or
1203              if they are empty.
1204        """
1205
1206        if not variables or not inverse_variables:
1207            raise TypeError("The Inverse constraint does not accept empty arrays")
1208        if len(variables) != len(inverse_variables):
1209            raise TypeError(
1210                "In the inverse constraint, the two array variables and"
1211                " inverse_variables must have the same length."
1212            )
1213        ct = Constraint(self)
1214        model_ct = self.__model.constraints[ct.index]
1215        model_ct.inverse.f_direct.extend([self.get_or_make_index(x) for x in variables])
1216        model_ct.inverse.f_inverse.extend(
1217            [self.get_or_make_index(x) for x in inverse_variables]
1218        )
1219        return ct
1220
1221    def add_reservoir_constraint(
1222        self,
1223        times: Iterable[LinearExprT],
1224        level_changes: Iterable[LinearExprT],
1225        min_level: int,
1226        max_level: int,
1227    ) -> Constraint:
1228        """Adds Reservoir(times, level_changes, min_level, max_level).
1229
1230        Maintains a reservoir level within bounds. The water level starts at 0, and
1231        at any time, it must be between min_level and max_level.
1232
1233        If the affine expression `times[i]` is assigned a value t, then the current
1234        level changes by `level_changes[i]`, which is constant, at time t.
1235
1236         Note that min level must be <= 0, and the max level must be >= 0. Please
1237         use fixed level_changes to simulate initial state.
1238
1239         Therefore, at any time:
1240             sum(level_changes[i] if times[i] <= t) in [min_level, max_level]
1241
1242        Args:
1243          times: A list of 1-var affine expressions (a * x + b) which specify the
1244            time of the filling or emptying the reservoir.
1245          level_changes: A list of integer values that specifies the amount of the
1246            emptying or filling. Currently, variable demands are not supported.
1247          min_level: At any time, the level of the reservoir must be greater or
1248            equal than the min level.
1249          max_level: At any time, the level of the reservoir must be less or equal
1250            than the max level.
1251
1252        Returns:
1253          An instance of the `Constraint` class.
1254
1255        Raises:
1256          ValueError: if max_level < min_level.
1257
1258          ValueError: if max_level < 0.
1259
1260          ValueError: if min_level > 0
1261        """
1262
1263        if max_level < min_level:
1264            raise ValueError("Reservoir constraint must have a max_level >= min_level")
1265
1266        if max_level < 0:
1267            raise ValueError("Reservoir constraint must have a max_level >= 0")
1268
1269        if min_level > 0:
1270            raise ValueError("Reservoir constraint must have a min_level <= 0")
1271
1272        ct = Constraint(self)
1273        model_ct = self.__model.constraints[ct.index]
1274        model_ct.reservoir.time_exprs.extend(
1275            [self.parse_linear_expression(x) for x in times]
1276        )
1277        model_ct.reservoir.level_changes.extend(
1278            [self.parse_linear_expression(x) for x in level_changes]
1279        )
1280        model_ct.reservoir.min_level = min_level
1281        model_ct.reservoir.max_level = max_level
1282        return ct
1283
1284    def add_reservoir_constraint_with_active(
1285        self,
1286        times: Iterable[LinearExprT],
1287        level_changes: Iterable[LinearExprT],
1288        actives: Iterable[LiteralT],
1289        min_level: int,
1290        max_level: int,
1291    ) -> Constraint:
1292        """Adds Reservoir(times, level_changes, actives, min_level, max_level).
1293
1294        Maintains a reservoir level within bounds. The water level starts at 0, and
1295        at any time, it must be between min_level and max_level.
1296
1297        If the variable `times[i]` is assigned a value t, and `actives[i]` is
1298        `True`, then the current level changes by `level_changes[i]`, which is
1299        constant,
1300        at time t.
1301
1302         Note that min level must be <= 0, and the max level must be >= 0. Please
1303         use fixed level_changes to simulate initial state.
1304
1305         Therefore, at any time:
1306             sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level,
1307             max_level]
1308
1309
1310        The array of boolean variables 'actives', if defined, indicates which
1311        actions are actually performed.
1312
1313        Args:
1314          times: A list of 1-var affine expressions (a * x + b) which specify the
1315            time of the filling or emptying the reservoir.
1316          level_changes: A list of integer values that specifies the amount of the
1317            emptying or filling. Currently, variable demands are not supported.
1318          actives: a list of boolean variables. They indicates if the
1319            emptying/refilling events actually take place.
1320          min_level: At any time, the level of the reservoir must be greater or
1321            equal than the min level.
1322          max_level: At any time, the level of the reservoir must be less or equal
1323            than the max level.
1324
1325        Returns:
1326          An instance of the `Constraint` class.
1327
1328        Raises:
1329          ValueError: if max_level < min_level.
1330
1331          ValueError: if max_level < 0.
1332
1333          ValueError: if min_level > 0
1334        """
1335
1336        if max_level < min_level:
1337            raise ValueError("Reservoir constraint must have a max_level >= min_level")
1338
1339        if max_level < 0:
1340            raise ValueError("Reservoir constraint must have a max_level >= 0")
1341
1342        if min_level > 0:
1343            raise ValueError("Reservoir constraint must have a min_level <= 0")
1344
1345        ct = Constraint(self)
1346        model_ct = self.__model.constraints[ct.index]
1347        model_ct.reservoir.time_exprs.extend(
1348            [self.parse_linear_expression(x) for x in times]
1349        )
1350        model_ct.reservoir.level_changes.extend(
1351            [self.parse_linear_expression(x) for x in level_changes]
1352        )
1353        model_ct.reservoir.active_literals.extend(
1354            [self.get_or_make_boolean_index(x) for x in actives]
1355        )
1356        model_ct.reservoir.min_level = min_level
1357        model_ct.reservoir.max_level = max_level
1358        return ct
1359
1360    def add_map_domain(
1361        self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0
1362    ):
1363        """Adds `var == i + offset <=> bool_var_array[i] == true for all i`."""
1364
1365        for i, bool_var in enumerate(bool_var_array):
1366            b_index = bool_var.index
1367            var_index = var.index
1368            model_ct = self.__model.constraints.add()
1369            model_ct.linear.vars.append(var_index)
1370            model_ct.linear.coeffs.append(1)
1371            offset_as_int = int(offset)
1372            model_ct.linear.domain.extend([offset_as_int + i, offset_as_int + i])
1373            model_ct.enforcement_literal.append(b_index)
1374
1375            model_ct = self.__model.constraints.add()
1376            model_ct.linear.vars.append(var_index)
1377            model_ct.linear.coeffs.append(1)
1378            model_ct.enforcement_literal.append(-b_index - 1)
1379            if offset + i - 1 >= INT_MIN:
1380                model_ct.linear.domain.extend([INT_MIN, offset_as_int + i - 1])
1381            if offset + i + 1 <= INT_MAX:
1382                model_ct.linear.domain.extend([offset_as_int + i + 1, INT_MAX])
1383
1384    def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint:
1385        """Adds `a => b` (`a` implies `b`)."""
1386        ct = Constraint(self)
1387        model_ct = self.__model.constraints[ct.index]
1388        model_ct.bool_or.literals.append(self.get_or_make_boolean_index(b))
1389        model_ct.enforcement_literal.append(self.get_or_make_boolean_index(a))
1390        return ct
1391
1392    @overload
1393    def add_bool_or(self, literals: Iterable[LiteralT]) -> Constraint: ...
1394
1395    @overload
1396    def add_bool_or(self, *literals: LiteralT) -> Constraint: ...
1397
1398    def add_bool_or(self, *literals):
1399        """Adds `Or(literals) == true`: sum(literals) >= 1."""
1400        ct = Constraint(self)
1401        model_ct = self.__model.constraints[ct.index]
1402        model_ct.bool_or.literals.extend(
1403            [
1404                self.get_or_make_boolean_index(x)
1405                for x in expand_generator_or_tuple(literals)
1406            ]
1407        )
1408        return ct
1409
1410    @overload
1411    def add_at_least_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
1412
1413    @overload
1414    def add_at_least_one(self, *literals: LiteralT) -> Constraint: ...
1415
1416    def add_at_least_one(self, *literals):
1417        """Same as `add_bool_or`: `sum(literals) >= 1`."""
1418        return self.add_bool_or(*literals)
1419
1420    @overload
1421    def add_at_most_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
1422
1423    @overload
1424    def add_at_most_one(self, *literals: LiteralT) -> Constraint: ...
1425
1426    def add_at_most_one(self, *literals):
1427        """Adds `AtMostOne(literals)`: `sum(literals) <= 1`."""
1428        ct = Constraint(self)
1429        model_ct = self.__model.constraints[ct.index]
1430        model_ct.at_most_one.literals.extend(
1431            [
1432                self.get_or_make_boolean_index(x)
1433                for x in expand_generator_or_tuple(literals)
1434            ]
1435        )
1436        return ct
1437
1438    @overload
1439    def add_exactly_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
1440
1441    @overload
1442    def add_exactly_one(self, *literals: LiteralT) -> Constraint: ...
1443
1444    def add_exactly_one(self, *literals):
1445        """Adds `ExactlyOne(literals)`: `sum(literals) == 1`."""
1446        ct = Constraint(self)
1447        model_ct = self.__model.constraints[ct.index]
1448        model_ct.exactly_one.literals.extend(
1449            [
1450                self.get_or_make_boolean_index(x)
1451                for x in expand_generator_or_tuple(literals)
1452            ]
1453        )
1454        return ct
1455
1456    @overload
1457    def add_bool_and(self, literals: Iterable[LiteralT]) -> Constraint: ...
1458
1459    @overload
1460    def add_bool_and(self, *literals: LiteralT) -> Constraint: ...
1461
1462    def add_bool_and(self, *literals):
1463        """Adds `And(literals) == true`."""
1464        ct = Constraint(self)
1465        model_ct = self.__model.constraints[ct.index]
1466        model_ct.bool_and.literals.extend(
1467            [
1468                self.get_or_make_boolean_index(x)
1469                for x in expand_generator_or_tuple(literals)
1470            ]
1471        )
1472        return ct
1473
1474    @overload
1475    def add_bool_xor(self, literals: Iterable[LiteralT]) -> Constraint: ...
1476
1477    @overload
1478    def add_bool_xor(self, *literals: LiteralT) -> Constraint: ...
1479
1480    def add_bool_xor(self, *literals):
1481        """Adds `XOr(literals) == true`.
1482
1483        In contrast to add_bool_or and add_bool_and, it does not support
1484            .only_enforce_if().
1485
1486        Args:
1487          *literals: the list of literals in the constraint.
1488
1489        Returns:
1490          An `Constraint` object.
1491        """
1492        ct = Constraint(self)
1493        model_ct = self.__model.constraints[ct.index]
1494        model_ct.bool_xor.literals.extend(
1495            [
1496                self.get_or_make_boolean_index(x)
1497                for x in expand_generator_or_tuple(literals)
1498            ]
1499        )
1500        return ct
1501
1502    def add_min_equality(
1503        self, target: LinearExprT, exprs: Iterable[LinearExprT]
1504    ) -> Constraint:
1505        """Adds `target == Min(exprs)`."""
1506        ct = Constraint(self)
1507        model_ct = self.__model.constraints[ct.index]
1508        model_ct.lin_max.exprs.extend(
1509            [self.parse_linear_expression(x, True) for x in exprs]
1510        )
1511        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target, True))
1512        return ct
1513
1514    def add_max_equality(
1515        self, target: LinearExprT, exprs: Iterable[LinearExprT]
1516    ) -> Constraint:
1517        """Adds `target == Max(exprs)`."""
1518        ct = Constraint(self)
1519        model_ct = self.__model.constraints[ct.index]
1520        model_ct.lin_max.exprs.extend([self.parse_linear_expression(x) for x in exprs])
1521        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
1522        return ct
1523
1524    def add_division_equality(
1525        self, target: LinearExprT, num: LinearExprT, denom: LinearExprT
1526    ) -> Constraint:
1527        """Adds `target == num // denom` (integer division rounded towards 0)."""
1528        ct = Constraint(self)
1529        model_ct = self.__model.constraints[ct.index]
1530        model_ct.int_div.exprs.append(self.parse_linear_expression(num))
1531        model_ct.int_div.exprs.append(self.parse_linear_expression(denom))
1532        model_ct.int_div.target.CopyFrom(self.parse_linear_expression(target))
1533        return ct
1534
1535    def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint:
1536        """Adds `target == Abs(expr)`."""
1537        ct = Constraint(self)
1538        model_ct = self.__model.constraints[ct.index]
1539        model_ct.lin_max.exprs.append(self.parse_linear_expression(expr))
1540        model_ct.lin_max.exprs.append(self.parse_linear_expression(expr, True))
1541        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
1542        return ct
1543
1544    def add_modulo_equality(
1545        self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT
1546    ) -> Constraint:
1547        """Adds `target = expr % mod`.
1548
1549        It uses the C convention, that is the result is the remainder of the
1550        integral division rounded towards 0.
1551
1552            For example:
1553            * 10 % 3 = 1
1554            * -10 % 3 = -1
1555            * 10 % -3 = 1
1556            * -10 % -3 = -1
1557
1558        Args:
1559          target: the target expression.
1560          expr: the expression to compute the modulo of.
1561          mod: the modulus expression.
1562
1563        Returns:
1564          A `Constraint` object.
1565        """
1566        ct = Constraint(self)
1567        model_ct = self.__model.constraints[ct.index]
1568        model_ct.int_mod.exprs.append(self.parse_linear_expression(expr))
1569        model_ct.int_mod.exprs.append(self.parse_linear_expression(mod))
1570        model_ct.int_mod.target.CopyFrom(self.parse_linear_expression(target))
1571        return ct
1572
1573    def add_multiplication_equality(
1574        self,
1575        target: LinearExprT,
1576        *expressions: Union[Iterable[LinearExprT], LinearExprT],
1577    ) -> Constraint:
1578        """Adds `target == expressions[0] * .. * expressions[n]`."""
1579        ct = Constraint(self)
1580        model_ct = self.__model.constraints[ct.index]
1581        model_ct.int_prod.exprs.extend(
1582            [
1583                self.parse_linear_expression(expr)
1584                for expr in expand_generator_or_tuple(expressions)
1585            ]
1586        )
1587        model_ct.int_prod.target.CopyFrom(self.parse_linear_expression(target))
1588        return ct
1589
1590    # Scheduling support
1591
1592    def new_interval_var(
1593        self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str
1594    ) -> IntervalVar:
1595        """Creates an interval variable from start, size, and end.
1596
1597        An interval variable is a constraint, that is itself used in other
1598        constraints like NoOverlap.
1599
1600        Internally, it ensures that `start + size == end`.
1601
1602        Args:
1603          start: The start of the interval. It must be of the form a * var + b.
1604          size: The size of the interval. It must be of the form a * var + b.
1605          end: The end of the interval. It must be of the form a * var + b.
1606          name: The name of the interval variable.
1607
1608        Returns:
1609          An `IntervalVar` object.
1610        """
1611
1612        start_expr = self.parse_linear_expression(start)
1613        size_expr = self.parse_linear_expression(size)
1614        end_expr = self.parse_linear_expression(end)
1615        if len(start_expr.vars) > 1:
1616            raise TypeError(
1617                "cp_model.new_interval_var: start must be 1-var affine or constant."
1618            )
1619        if len(size_expr.vars) > 1:
1620            raise TypeError(
1621                "cp_model.new_interval_var: size must be 1-var affine or constant."
1622            )
1623        if len(end_expr.vars) > 1:
1624            raise TypeError(
1625                "cp_model.new_interval_var: end must be 1-var affine or constant."
1626            )
1627        return IntervalVar(
1628            self.__model,
1629            self.__var_list,
1630            start_expr,
1631            size_expr,
1632            end_expr,
1633            None,
1634            name,
1635        )
1636
1637    def new_interval_var_series(
1638        self,
1639        name: str,
1640        index: pd.Index,
1641        starts: Union[LinearExprT, pd.Series],
1642        sizes: Union[LinearExprT, pd.Series],
1643        ends: Union[LinearExprT, pd.Series],
1644    ) -> pd.Series:
1645        """Creates a series of interval variables with the given name.
1646
1647        Args:
1648          name (str): Required. The name of the variable set.
1649          index (pd.Index): Required. The index to use for the variable set.
1650          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1651            set. If a `pd.Series` is passed in, it will be based on the
1652            corresponding values of the pd.Series.
1653          sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1654            set. If a `pd.Series` is passed in, it will be based on the
1655            corresponding values of the pd.Series.
1656          ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1657            set. If a `pd.Series` is passed in, it will be based on the
1658            corresponding values of the pd.Series.
1659
1660        Returns:
1661          pd.Series: The interval variable set indexed by its corresponding
1662          dimensions.
1663
1664        Raises:
1665          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1666          ValueError: if the `name` is not a valid identifier or already exists.
1667          ValueError: if the all the indexes do not match.
1668        """
1669        if not isinstance(index, pd.Index):
1670            raise TypeError("Non-index object is used as index")
1671        if not name.isidentifier():
1672            raise ValueError(f"name={name!r} is not a valid identifier")
1673
1674        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1675        sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
1676        ends = _convert_to_linear_expr_series_and_validate_index(ends, index)
1677        interval_array = []
1678        for i in index:
1679            interval_array.append(
1680                self.new_interval_var(
1681                    start=starts[i],
1682                    size=sizes[i],
1683                    end=ends[i],
1684                    name=f"{name}[{i}]",
1685                )
1686            )
1687        return pd.Series(index=index, data=interval_array)
1688
1689    def new_fixed_size_interval_var(
1690        self, start: LinearExprT, size: IntegralT, name: str
1691    ) -> IntervalVar:
1692        """Creates an interval variable from start, and a fixed size.
1693
1694        An interval variable is a constraint, that is itself used in other
1695        constraints like NoOverlap.
1696
1697        Args:
1698          start: The start of the interval. It must be of the form a * var + b.
1699          size: The size of the interval. It must be an integer value.
1700          name: The name of the interval variable.
1701
1702        Returns:
1703          An `IntervalVar` object.
1704        """
1705        start_expr = self.parse_linear_expression(start)
1706        size_expr = self.parse_linear_expression(size)
1707        end_expr = self.parse_linear_expression(start + size)
1708        if len(start_expr.vars) > 1:
1709            raise TypeError(
1710                "cp_model.new_interval_var: start must be affine or constant."
1711            )
1712        return IntervalVar(
1713            self.__model,
1714            self.__var_list,
1715            start_expr,
1716            size_expr,
1717            end_expr,
1718            None,
1719            name,
1720        )
1721
1722    def new_fixed_size_interval_var_series(
1723        self,
1724        name: str,
1725        index: pd.Index,
1726        starts: Union[LinearExprT, pd.Series],
1727        sizes: Union[IntegralT, pd.Series],
1728    ) -> pd.Series:
1729        """Creates a series of interval variables with the given name.
1730
1731        Args:
1732          name (str): Required. The name of the variable set.
1733          index (pd.Index): Required. The index to use for the variable set.
1734          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1735            set. If a `pd.Series` is passed in, it will be based on the
1736            corresponding values of the pd.Series.
1737          sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1738            the set. If a `pd.Series` is passed in, it will be based on the
1739            corresponding values of the pd.Series.
1740
1741        Returns:
1742          pd.Series: The interval variable set indexed by its corresponding
1743          dimensions.
1744
1745        Raises:
1746          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1747          ValueError: if the `name` is not a valid identifier or already exists.
1748          ValueError: if the all the indexes do not match.
1749        """
1750        if not isinstance(index, pd.Index):
1751            raise TypeError("Non-index object is used as index")
1752        if not name.isidentifier():
1753            raise ValueError(f"name={name!r} is not a valid identifier")
1754
1755        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1756        sizes = _convert_to_integral_series_and_validate_index(sizes, index)
1757        interval_array = []
1758        for i in index:
1759            interval_array.append(
1760                self.new_fixed_size_interval_var(
1761                    start=starts[i],
1762                    size=sizes[i],
1763                    name=f"{name}[{i}]",
1764                )
1765            )
1766        return pd.Series(index=index, data=interval_array)
1767
1768    def new_optional_interval_var(
1769        self,
1770        start: LinearExprT,
1771        size: LinearExprT,
1772        end: LinearExprT,
1773        is_present: LiteralT,
1774        name: str,
1775    ) -> IntervalVar:
1776        """Creates an optional interval var from start, size, end, and is_present.
1777
1778        An optional interval variable is a constraint, that is itself used in other
1779        constraints like NoOverlap. This constraint is protected by a presence
1780        literal that indicates if it is active or not.
1781
1782        Internally, it ensures that `is_present` implies `start + size ==
1783        end`.
1784
1785        Args:
1786          start: The start of the interval. It must be of the form a * var + b.
1787          size: The size of the interval. It must be of the form a * var + b.
1788          end: The end of the interval. It must be of the form a * var + b.
1789          is_present: A literal that indicates if the interval is active or not. A
1790            inactive interval is simply ignored by all constraints.
1791          name: The name of the interval variable.
1792
1793        Returns:
1794          An `IntervalVar` object.
1795        """
1796
1797        # Creates the IntervalConstraintProto object.
1798        is_present_index = self.get_or_make_boolean_index(is_present)
1799        start_expr = self.parse_linear_expression(start)
1800        size_expr = self.parse_linear_expression(size)
1801        end_expr = self.parse_linear_expression(end)
1802        if len(start_expr.vars) > 1:
1803            raise TypeError(
1804                "cp_model.new_interval_var: start must be affine or constant."
1805            )
1806        if len(size_expr.vars) > 1:
1807            raise TypeError(
1808                "cp_model.new_interval_var: size must be affine or constant."
1809            )
1810        if len(end_expr.vars) > 1:
1811            raise TypeError(
1812                "cp_model.new_interval_var: end must be affine or constant."
1813            )
1814        return IntervalVar(
1815            self.__model,
1816            self.__var_list,
1817            start_expr,
1818            size_expr,
1819            end_expr,
1820            is_present_index,
1821            name,
1822        )
1823
1824    def new_optional_interval_var_series(
1825        self,
1826        name: str,
1827        index: pd.Index,
1828        starts: Union[LinearExprT, pd.Series],
1829        sizes: Union[LinearExprT, pd.Series],
1830        ends: Union[LinearExprT, pd.Series],
1831        are_present: Union[LiteralT, pd.Series],
1832    ) -> pd.Series:
1833        """Creates a series of interval variables with the given name.
1834
1835        Args:
1836          name (str): Required. The name of the variable set.
1837          index (pd.Index): Required. The index to use for the variable set.
1838          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1839            set. If a `pd.Series` is passed in, it will be based on the
1840            corresponding values of the pd.Series.
1841          sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1842            set. If a `pd.Series` is passed in, it will be based on the
1843            corresponding values of the pd.Series.
1844          ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1845            set. If a `pd.Series` is passed in, it will be based on the
1846            corresponding values of the pd.Series.
1847          are_present (Union[LiteralT, pd.Series]): The performed literal of each
1848            interval in the set. If a `pd.Series` is passed in, it will be based on
1849            the corresponding values of the pd.Series.
1850
1851        Returns:
1852          pd.Series: The interval variable set indexed by its corresponding
1853          dimensions.
1854
1855        Raises:
1856          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1857          ValueError: if the `name` is not a valid identifier or already exists.
1858          ValueError: if the all the indexes do not match.
1859        """
1860        if not isinstance(index, pd.Index):
1861            raise TypeError("Non-index object is used as index")
1862        if not name.isidentifier():
1863            raise ValueError(f"name={name!r} is not a valid identifier")
1864
1865        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1866        sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
1867        ends = _convert_to_linear_expr_series_and_validate_index(ends, index)
1868        are_present = _convert_to_literal_series_and_validate_index(are_present, index)
1869
1870        interval_array = []
1871        for i in index:
1872            interval_array.append(
1873                self.new_optional_interval_var(
1874                    start=starts[i],
1875                    size=sizes[i],
1876                    end=ends[i],
1877                    is_present=are_present[i],
1878                    name=f"{name}[{i}]",
1879                )
1880            )
1881        return pd.Series(index=index, data=interval_array)
1882
1883    def new_optional_fixed_size_interval_var(
1884        self,
1885        start: LinearExprT,
1886        size: IntegralT,
1887        is_present: LiteralT,
1888        name: str,
1889    ) -> IntervalVar:
1890        """Creates an interval variable from start, and a fixed size.
1891
1892        An interval variable is a constraint, that is itself used in other
1893        constraints like NoOverlap.
1894
1895        Args:
1896          start: The start of the interval. It must be of the form a * var + b.
1897          size: The size of the interval. It must be an integer value.
1898          is_present: A literal that indicates if the interval is active or not. A
1899            inactive interval is simply ignored by all constraints.
1900          name: The name of the interval variable.
1901
1902        Returns:
1903          An `IntervalVar` object.
1904        """
1905        start_expr = self.parse_linear_expression(start)
1906        size_expr = self.parse_linear_expression(size)
1907        end_expr = self.parse_linear_expression(start + size)
1908        if len(start_expr.vars) > 1:
1909            raise TypeError(
1910                "cp_model.new_interval_var: start must be affine or constant."
1911            )
1912        is_present_index = self.get_or_make_boolean_index(is_present)
1913        return IntervalVar(
1914            self.__model,
1915            self.__var_list,
1916            start_expr,
1917            size_expr,
1918            end_expr,
1919            is_present_index,
1920            name,
1921        )
1922
1923    def new_optional_fixed_size_interval_var_series(
1924        self,
1925        name: str,
1926        index: pd.Index,
1927        starts: Union[LinearExprT, pd.Series],
1928        sizes: Union[IntegralT, pd.Series],
1929        are_present: Union[LiteralT, pd.Series],
1930    ) -> pd.Series:
1931        """Creates a series of interval variables with the given name.
1932
1933        Args:
1934          name (str): Required. The name of the variable set.
1935          index (pd.Index): Required. The index to use for the variable set.
1936          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1937            set. If a `pd.Series` is passed in, it will be based on the
1938            corresponding values of the pd.Series.
1939          sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1940            the set. If a `pd.Series` is passed in, it will be based on the
1941            corresponding values of the pd.Series.
1942          are_present (Union[LiteralT, pd.Series]): The performed literal of each
1943            interval in the set. If a `pd.Series` is passed in, it will be based on
1944            the corresponding values of the pd.Series.
1945
1946        Returns:
1947          pd.Series: The interval variable set indexed by its corresponding
1948          dimensions.
1949
1950        Raises:
1951          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1952          ValueError: if the `name` is not a valid identifier or already exists.
1953          ValueError: if the all the indexes do not match.
1954        """
1955        if not isinstance(index, pd.Index):
1956            raise TypeError("Non-index object is used as index")
1957        if not name.isidentifier():
1958            raise ValueError(f"name={name!r} is not a valid identifier")
1959
1960        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1961        sizes = _convert_to_integral_series_and_validate_index(sizes, index)
1962        are_present = _convert_to_literal_series_and_validate_index(are_present, index)
1963        interval_array = []
1964        for i in index:
1965            interval_array.append(
1966                self.new_optional_fixed_size_interval_var(
1967                    start=starts[i],
1968                    size=sizes[i],
1969                    is_present=are_present[i],
1970                    name=f"{name}[{i}]",
1971                )
1972            )
1973        return pd.Series(index=index, data=interval_array)
1974
1975    def add_no_overlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint:
1976        """Adds NoOverlap(interval_vars).
1977
1978        A NoOverlap constraint ensures that all present intervals do not overlap
1979        in time.
1980
1981        Args:
1982          interval_vars: The list of interval variables to constrain.
1983
1984        Returns:
1985          An instance of the `Constraint` class.
1986        """
1987        ct = Constraint(self)
1988        model_ct = self.__model.constraints[ct.index]
1989        model_ct.no_overlap.intervals.extend(
1990            [self.get_interval_index(x) for x in interval_vars]
1991        )
1992        return ct
1993
1994    def add_no_overlap_2d(
1995        self,
1996        x_intervals: Iterable[IntervalVar],
1997        y_intervals: Iterable[IntervalVar],
1998    ) -> Constraint:
1999        """Adds NoOverlap2D(x_intervals, y_intervals).
2000
2001        A NoOverlap2D constraint ensures that all present rectangles do not overlap
2002        on a plane. Each rectangle is aligned with the X and Y axis, and is defined
2003        by two intervals which represent its projection onto the X and Y axis.
2004
2005        Furthermore, one box is optional if at least one of the x or y interval is
2006        optional.
2007
2008        Args:
2009          x_intervals: The X coordinates of the rectangles.
2010          y_intervals: The Y coordinates of the rectangles.
2011
2012        Returns:
2013          An instance of the `Constraint` class.
2014        """
2015        ct = Constraint(self)
2016        model_ct = self.__model.constraints[ct.index]
2017        model_ct.no_overlap_2d.x_intervals.extend(
2018            [self.get_interval_index(x) for x in x_intervals]
2019        )
2020        model_ct.no_overlap_2d.y_intervals.extend(
2021            [self.get_interval_index(x) for x in y_intervals]
2022        )
2023        return ct
2024
2025    def add_cumulative(
2026        self,
2027        intervals: Iterable[IntervalVar],
2028        demands: Iterable[LinearExprT],
2029        capacity: LinearExprT,
2030    ) -> Constraint:
2031        """Adds Cumulative(intervals, demands, capacity).
2032
2033        This constraint enforces that:
2034
2035            for all t:
2036              sum(demands[i]
2037                if (start(intervals[i]) <= t < end(intervals[i])) and
2038                (intervals[i] is present)) <= capacity
2039
2040        Args:
2041          intervals: The list of intervals.
2042          demands: The list of demands for each interval. Each demand must be >= 0.
2043            Each demand can be a 1-var affine expression (a * x + b).
2044          capacity: The maximum capacity of the cumulative constraint. It can be a
2045            1-var affine expression (a * x + b).
2046
2047        Returns:
2048          An instance of the `Constraint` class.
2049        """
2050        cumulative = Constraint(self)
2051        model_ct = self.__model.constraints[cumulative.index]
2052        model_ct.cumulative.intervals.extend(
2053            [self.get_interval_index(x) for x in intervals]
2054        )
2055        for d in demands:
2056            model_ct.cumulative.demands.append(self.parse_linear_expression(d))
2057        model_ct.cumulative.capacity.CopyFrom(self.parse_linear_expression(capacity))
2058        return cumulative
2059
2060    # Support for model cloning.
2061    def clone(self) -> "CpModel":
2062        """Reset the model, and creates a new one from a CpModelProto instance."""
2063        clone = CpModel()
2064        clone.proto.CopyFrom(self.proto)
2065        clone.rebuild_var_and_constant_map()
2066        return clone
2067
2068    def __copy__(self):
2069        return self.clone()
2070
2071    def __deepcopy__(self, memo):
2072        return self.clone()
2073
2074    def rebuild_var_and_constant_map(self):
2075        """Internal method used during model cloning."""
2076        for i, var in enumerate(self.__model.variables):
2077            if len(var.domain) == 2 and var.domain[0] == var.domain[1]:
2078                self.__constant_map[var.domain[0]] = i
2079            is_boolean = (
2080                len(var.domain) == 2 and var.domain[0] >= 0 and var.domain[1] <= 1
2081            )
2082            self.__var_list.append(IntVar(self.__model, i, is_boolean, None))
2083
2084    def get_bool_var_from_proto_index(self, index: int) -> IntVar:
2085        """Returns an already created Boolean variable from its index."""
2086        result = self._get_int_var(index)
2087        if not result.is_boolean:
2088            raise ValueError(
2089                f"get_bool_var_from_proto_index: index {index} does not reference a"
2090                " boolean variable"
2091            )
2092        return result
2093
2094    def get_int_var_from_proto_index(self, index: int) -> IntVar:
2095        """Returns an already created integer variable from its index."""
2096        return self._get_int_var(index)
2097
2098    def get_interval_var_from_proto_index(self, index: int) -> IntervalVar:
2099        """Returns an already created interval variable from its index."""
2100        if index < 0 or index >= len(self.__model.constraints):
2101            raise ValueError(
2102                f"get_interval_var_from_proto_index: out of bound index {index}"
2103            )
2104        ct = self.__model.constraints[index]
2105        if not ct.HasField("interval"):
2106            raise ValueError(
2107                f"get_interval_var_from_proto_index: index {index} does not"
2108                " reference an" + " interval variable"
2109            )
2110
2111        return IntervalVar(self.__model, self.__var_list, index, None, None, None, None)
2112
2113    # Helpers.
2114
2115    def __str__(self) -> str:
2116        return str(self.__model)
2117
2118    @property
2119    def proto(self) -> cp_model_pb2.CpModelProto:
2120        """Returns the underlying CpModelProto."""
2121        return self.__model
2122
2123    def negated(self, index: int) -> int:
2124        return -index - 1
2125
2126    def get_or_make_index(self, arg: VariableT) -> int:
2127        """Returns the index of a variable, its negation, or a number."""
2128        if isinstance(arg, IntVar):
2129            return arg.index
2130        if isinstance(arg, IntegralTypes):
2131            return self.get_or_make_index_from_constant(arg)
2132        raise TypeError(
2133            f"NotSupported: model.get_or_make_index({type(arg).__name__!r})"
2134        )
2135
2136    def get_or_make_boolean_index(self, arg: LiteralT) -> int:
2137        """Returns an index from a boolean expression."""
2138        if isinstance(arg, IntVar):
2139            self.assert_is_boolean_variable(arg)
2140            return arg.index
2141        if isinstance(arg, cmh.NotBooleanVariable):
2142            self.assert_is_boolean_variable(arg.negated())
2143            return arg.index
2144        if isinstance(arg, IntegralTypes):
2145            if arg == ~False:  # -1
2146                return self.get_or_make_index_from_constant(1)
2147            if arg == ~True:  # -2
2148                return self.get_or_make_index_from_constant(0)
2149            arg = cmn.assert_is_zero_or_one(arg)
2150            return self.get_or_make_index_from_constant(arg)
2151        if cmn.is_boolean(arg):
2152            return self.get_or_make_index_from_constant(int(arg))
2153        raise TypeError(
2154            "not supported:" f" model.get_or_make_boolean_index({type(arg).__name__!r})"
2155        )
2156
2157    def get_interval_index(self, arg: IntervalVar) -> int:
2158        if not isinstance(arg, IntervalVar):
2159            raise TypeError(
2160                f"NotSupported: model.get_interval_index({type(arg).__name__!r})"
2161            )
2162        return arg.index
2163
2164    def get_or_make_index_from_constant(self, value: IntegralT) -> int:
2165        if value in self.__constant_map:
2166            return self.__constant_map[value]
2167        constant_var = self.new_int_var(value, value, "")
2168        self.__constant_map[value] = constant_var.index
2169        return constant_var.index
2170
2171    def parse_linear_expression(
2172        self, linear_expr: LinearExprT, negate: bool = False
2173    ) -> cp_model_pb2.LinearExpressionProto:
2174        """Returns a LinearExpressionProto built from a LinearExpr instance."""
2175        result: cp_model_pb2.LinearExpressionProto = (
2176            cp_model_pb2.LinearExpressionProto()
2177        )
2178        mult = -1 if negate else 1
2179        if isinstance(linear_expr, IntegralTypes):
2180            result.offset = int(linear_expr) * mult
2181            return result
2182
2183        # Raises TypeError if linear_expr is not an integer.
2184        flat_expr = cmh.FlatIntExpr(linear_expr)
2185        result.offset = flat_expr.offset
2186        for var in flat_expr.vars:
2187            result.vars.append(var.index)
2188        for coeff in flat_expr.coeffs:
2189            result.coeffs.append(coeff * mult)
2190        return result
2191
2192    def _set_objective(self, obj: ObjLinearExprT, minimize: bool):
2193        """Sets the objective of the model."""
2194        self.clear_objective()
2195        if isinstance(obj, IntegralTypes):
2196            self.__model.objective.offset = int(obj)
2197            self.__model.objective.scaling_factor = 1.0
2198        elif isinstance(obj, LinearExpr):
2199            if obj.is_integer():
2200                int_obj = cmh.FlatIntExpr(obj)
2201                for var in int_obj.vars:
2202                    self.__model.objective.vars.append(var.index)
2203                if minimize:
2204                    self.__model.objective.scaling_factor = 1.0
2205                    self.__model.objective.offset = int_obj.offset
2206                    self.__model.objective.coeffs.extend(int_obj.coeffs)
2207                else:
2208                    self.__model.objective.scaling_factor = -1.0
2209                    self.__model.objective.offset = -int_obj.offset
2210                    for c in int_obj.coeffs:
2211                        self.__model.objective.coeffs.append(-c)
2212            else:
2213                float_obj = cmh.FlatFloatExpr(obj)
2214                for var in float_obj.vars:
2215                    self.__model.floating_point_objective.vars.append(var.index)
2216                self.__model.floating_point_objective.coeffs.extend(float_obj.coeffs)
2217                self.__model.floating_point_objective.maximize = not minimize
2218                self.__model.floating_point_objective.offset = float_obj.offset
2219        else:
2220            raise TypeError(
2221                f"TypeError: {type(obj).__name__!r} is not a valid objective"
2222            )
2223
2224    def minimize(self, obj: ObjLinearExprT):
2225        """Sets the objective of the model to minimize(obj)."""
2226        self._set_objective(obj, minimize=True)
2227
2228    def maximize(self, obj: ObjLinearExprT):
2229        """Sets the objective of the model to maximize(obj)."""
2230        self._set_objective(obj, minimize=False)
2231
2232    def has_objective(self) -> bool:
2233        return self.__model.HasField("objective") or self.__model.HasField(
2234            "floating_point_objective"
2235        )
2236
2237    def clear_objective(self):
2238        self.__model.ClearField("objective")
2239        self.__model.ClearField("floating_point_objective")
2240
2241    def add_decision_strategy(
2242        self,
2243        variables: Sequence[IntVar],
2244        var_strategy: cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy,
2245        domain_strategy: cp_model_pb2.DecisionStrategyProto.DomainReductionStrategy,
2246    ) -> None:
2247        """Adds a search strategy to the model.
2248
2249        Args:
2250          variables: a list of variables this strategy will assign.
2251          var_strategy: heuristic to choose the next variable to assign.
2252          domain_strategy: heuristic to reduce the domain of the selected variable.
2253            Currently, this is advanced code: the union of all strategies added to
2254            the model must be complete, i.e. instantiates all variables. Otherwise,
2255            solve() will fail.
2256        """
2257
2258        strategy: cp_model_pb2.DecisionStrategyProto = (
2259            self.__model.search_strategy.add()
2260        )
2261        for v in variables:
2262            expr = strategy.exprs.add()
2263            if v.index >= 0:
2264                expr.vars.append(v.index)
2265                expr.coeffs.append(1)
2266            else:
2267                expr.vars.append(self.negated(v.index))
2268                expr.coeffs.append(-1)
2269                expr.offset = 1
2270
2271        strategy.variable_selection_strategy = var_strategy
2272        strategy.domain_reduction_strategy = domain_strategy
2273
2274    def model_stats(self) -> str:
2275        """Returns a string containing some model statistics."""
2276        return cmh.CpSatHelper.model_stats(self.__model)
2277
2278    def validate(self) -> str:
2279        """Returns a string indicating that the model is invalid."""
2280        return cmh.CpSatHelper.validate_model(self.__model)
2281
2282    def export_to_file(self, file: str) -> bool:
2283        """Write the model as a protocol buffer to 'file'.
2284
2285        Args:
2286          file: file to write the model to. If the filename ends with 'txt', the
2287            model will be written as a text file, otherwise, the binary format will
2288            be used.
2289
2290        Returns:
2291          True if the model was correctly written.
2292        """
2293        return cmh.CpSatHelper.write_model_to_file(self.__model, file)
2294
2295    @overload
2296    def add_hint(self, var: IntVar, value: int) -> None: ...
2297
2298    @overload
2299    def add_hint(self, literal: BoolVarT, value: bool) -> None: ...
2300
2301    def add_hint(self, var, value) -> None:
2302        """Adds 'var == value' as a hint to the solver."""
2303        if var.index >= 0:
2304            self.__model.solution_hint.vars.append(self.get_or_make_index(var))
2305            self.__model.solution_hint.values.append(int(value))
2306        else:
2307            self.__model.solution_hint.vars.append(self.negated(var.index))
2308            self.__model.solution_hint.values.append(int(not value))
2309
2310    def clear_hints(self):
2311        """Removes any solution hint from the model."""
2312        self.__model.ClearField("solution_hint")
2313
2314    def add_assumption(self, lit: LiteralT) -> None:
2315        """Adds the literal to the model as assumptions."""
2316        self.__model.assumptions.append(self.get_or_make_boolean_index(lit))
2317
2318    def add_assumptions(self, literals: Iterable[LiteralT]) -> None:
2319        """Adds the literals to the model as assumptions."""
2320        for lit in literals:
2321            self.add_assumption(lit)
2322
2323    def clear_assumptions(self) -> None:
2324        """Removes all assumptions from the model."""
2325        self.__model.ClearField("assumptions")
2326
2327    # Helpers.
2328    def assert_is_boolean_variable(self, x: LiteralT) -> None:
2329        if isinstance(x, IntVar):
2330            var = self.__model.variables[x.index]
2331            if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1:
2332                raise TypeError(
2333                    f"TypeError: {type(x).__name__!r} is not a boolean variable"
2334                )
2335        elif not isinstance(x, cmh.NotBooleanVariable):
2336            raise TypeError(
2337                f"TypeError: {type(x).__name__!r}  is not a boolean variable"
2338            )
2339
2340    # Compatibility with pre PEP8
2341    # pylint: disable=invalid-name
2342
2343    def Name(self) -> str:
2344        return self.name
2345
2346    def SetName(self, name: str) -> None:
2347        self.name = name
2348
2349    def Proto(self) -> cp_model_pb2.CpModelProto:
2350        return self.proto
2351
2352    NewIntVar = new_int_var
2353    NewIntVarFromDomain = new_int_var_from_domain
2354    NewBoolVar = new_bool_var
2355    NewConstant = new_constant
2356    NewIntVarSeries = new_int_var_series
2357    NewBoolVarSeries = new_bool_var_series
2358    AddLinearConstraint = add_linear_constraint
2359    AddLinearExpressionInDomain = add_linear_expression_in_domain
2360    Add = add
2361    AddAllDifferent = add_all_different
2362    AddElement = add_element
2363    AddCircuit = add_circuit
2364    AddMultipleCircuit = add_multiple_circuit
2365    AddAllowedAssignments = add_allowed_assignments
2366    AddForbiddenAssignments = add_forbidden_assignments
2367    AddAutomaton = add_automaton
2368    AddInverse = add_inverse
2369    AddReservoirConstraint = add_reservoir_constraint
2370    AddReservoirConstraintWithActive = add_reservoir_constraint_with_active
2371    AddImplication = add_implication
2372    AddBoolOr = add_bool_or
2373    AddAtLeastOne = add_at_least_one
2374    AddAtMostOne = add_at_most_one
2375    AddExactlyOne = add_exactly_one
2376    AddBoolAnd = add_bool_and
2377    AddBoolXOr = add_bool_xor
2378    AddMinEquality = add_min_equality
2379    AddMaxEquality = add_max_equality
2380    AddDivisionEquality = add_division_equality
2381    AddAbsEquality = add_abs_equality
2382    AddModuloEquality = add_modulo_equality
2383    AddMultiplicationEquality = add_multiplication_equality
2384    NewIntervalVar = new_interval_var
2385    NewIntervalVarSeries = new_interval_var_series
2386    NewFixedSizeIntervalVar = new_fixed_size_interval_var
2387    NewOptionalIntervalVar = new_optional_interval_var
2388    NewOptionalIntervalVarSeries = new_optional_interval_var_series
2389    NewOptionalFixedSizeIntervalVar = new_optional_fixed_size_interval_var
2390    NewOptionalFixedSizeIntervalVarSeries = new_optional_fixed_size_interval_var_series
2391    AddNoOverlap = add_no_overlap
2392    AddNoOverlap2D = add_no_overlap_2d
2393    AddCumulative = add_cumulative
2394    Clone = clone
2395    GetBoolVarFromProtoIndex = get_bool_var_from_proto_index
2396    GetIntVarFromProtoIndex = get_int_var_from_proto_index
2397    GetIntervalVarFromProtoIndex = get_interval_var_from_proto_index
2398    Minimize = minimize
2399    Maximize = maximize
2400    HasObjective = has_objective
2401    ClearObjective = clear_objective
2402    AddDecisionStrategy = add_decision_strategy
2403    ModelStats = model_stats
2404    Validate = validate
2405    ExportToFile = export_to_file
2406    AddHint = add_hint
2407    ClearHints = clear_hints
2408    AddAssumption = add_assumption
2409    AddAssumptions = add_assumptions
2410    ClearAssumptions = clear_assumptions
2411
2412    # pylint: enable=invalid-name
2413
2414
2415@overload
2416def expand_generator_or_tuple(
2417    args: Union[Tuple[LiteralT, ...], Iterable[LiteralT]],
2418) -> Union[Iterable[LiteralT], LiteralT]: ...
2419
2420
2421@overload
2422def expand_generator_or_tuple(
2423    args: Union[Tuple[LinearExprT, ...], Iterable[LinearExprT]],
2424) -> Union[Iterable[LinearExprT], LinearExprT]: ...
2425
2426
2427def expand_generator_or_tuple(args):
2428    if hasattr(args, "__len__"):  # Tuple
2429        if len(args) != 1:
2430            return args
2431        if isinstance(args[0], (NumberTypes, LinearExpr)):
2432            return args
2433    # Generator
2434    return args[0]
2435
2436
2437class CpSolver:
2438    """Main solver class.
2439
2440    The purpose of this class is to search for a solution to the model provided
2441    to the solve() method.
2442
2443    Once solve() is called, this class allows inspecting the solution found
2444    with the value() and boolean_value() methods, as well as general statistics
2445    about the solve procedure.
2446    """
2447
2448    def __init__(self) -> None:
2449        self.__response_wrapper: Optional[cmh.ResponseWrapper] = None
2450        self.parameters: sat_parameters_pb2.SatParameters = (
2451            sat_parameters_pb2.SatParameters()
2452        )
2453        self.log_callback: Optional[Callable[[str], None]] = None
2454        self.best_bound_callback: Optional[Callable[[float], None]] = None
2455        self.__solve_wrapper: Optional[cmh.SolveWrapper] = None
2456        self.__lock: threading.Lock = threading.Lock()
2457
2458    def solve(
2459        self,
2460        model: CpModel,
2461        solution_callback: Optional["CpSolverSolutionCallback"] = None,
2462    ) -> cp_model_pb2.CpSolverStatus:
2463        """Solves a problem and passes each solution to the callback if not null."""
2464        with self.__lock:
2465            self.__solve_wrapper = cmh.SolveWrapper()
2466
2467        self.__solve_wrapper.set_parameters(self.parameters)
2468        if solution_callback is not None:
2469            self.__solve_wrapper.add_solution_callback(solution_callback)
2470
2471        if self.log_callback is not None:
2472            self.__solve_wrapper.add_log_callback(self.log_callback)
2473
2474        if self.best_bound_callback is not None:
2475            self.__solve_wrapper.add_best_bound_callback(self.best_bound_callback)
2476
2477        self.__response_wrapper = (
2478            self.__solve_wrapper.solve_and_return_response_wrapper(model.proto)
2479        )
2480
2481        if solution_callback is not None:
2482            self.__solve_wrapper.clear_solution_callback(solution_callback)
2483
2484        with self.__lock:
2485            self.__solve_wrapper = None
2486
2487        return self.__response_wrapper.status()
2488
2489    def stop_search(self) -> None:
2490        """Stops the current search asynchronously."""
2491        with self.__lock:
2492            if self.__solve_wrapper:
2493                self.__solve_wrapper.stop_search()
2494
2495    def value(self, expression: LinearExprT) -> int:
2496        """Returns the value of a linear expression after solve."""
2497        return self._checked_response.value(expression)
2498
2499    def values(self, variables: _IndexOrSeries) -> pd.Series:
2500        """Returns the values of the input variables.
2501
2502        If `variables` is a `pd.Index`, then the output will be indexed by the
2503        variables. If `variables` is a `pd.Series` indexed by the underlying
2504        dimensions, then the output will be indexed by the same underlying
2505        dimensions.
2506
2507        Args:
2508          variables (Union[pd.Index, pd.Series]): The set of variables from which to
2509            get the values.
2510
2511        Returns:
2512          pd.Series: The values of all variables in the set.
2513
2514        Raises:
2515          RuntimeError: if solve() has not been called.
2516        """
2517        if self.__response_wrapper is None:
2518            raise RuntimeError("solve() has not been called.")
2519        return pd.Series(
2520            data=[self.__response_wrapper.value(var) for var in variables],
2521            index=_get_index(variables),
2522        )
2523
2524    def boolean_value(self, literal: LiteralT) -> bool:
2525        """Returns the boolean value of a literal after solve."""
2526        return self._checked_response.boolean_value(literal)
2527
2528    def boolean_values(self, variables: _IndexOrSeries) -> pd.Series:
2529        """Returns the values of the input variables.
2530
2531        If `variables` is a `pd.Index`, then the output will be indexed by the
2532        variables. If `variables` is a `pd.Series` indexed by the underlying
2533        dimensions, then the output will be indexed by the same underlying
2534        dimensions.
2535
2536        Args:
2537          variables (Union[pd.Index, pd.Series]): The set of variables from which to
2538            get the values.
2539
2540        Returns:
2541          pd.Series: The values of all variables in the set.
2542
2543        Raises:
2544          RuntimeError: if solve() has not been called.
2545        """
2546        if self.__response_wrapper is None:
2547            raise RuntimeError("solve() has not been called.")
2548        return pd.Series(
2549            data=[
2550                self.__response_wrapper.boolean_value(literal) for literal in variables
2551            ],
2552            index=_get_index(variables),
2553        )
2554
2555    @property
2556    def objective_value(self) -> float:
2557        """Returns the value of the objective after solve."""
2558        return self._checked_response.objective_value()
2559
2560    @property
2561    def best_objective_bound(self) -> float:
2562        """Returns the best lower (upper) bound found when min(max)imizing."""
2563        return self._checked_response.best_objective_bound()
2564
2565    @property
2566    def num_booleans(self) -> int:
2567        """Returns the number of boolean variables managed by the SAT solver."""
2568        return self._checked_response.num_booleans()
2569
2570    @property
2571    def num_conflicts(self) -> int:
2572        """Returns the number of conflicts since the creation of the solver."""
2573        return self._checked_response.num_conflicts()
2574
2575    @property
2576    def num_branches(self) -> int:
2577        """Returns the number of search branches explored by the solver."""
2578        return self._checked_response.num_branches()
2579
2580    @property
2581    def wall_time(self) -> float:
2582        """Returns the wall time in seconds since the creation of the solver."""
2583        return self._checked_response.wall_time()
2584
2585    @property
2586    def user_time(self) -> float:
2587        """Returns the user time in seconds since the creation of the solver."""
2588        return self._checked_response.user_time()
2589
2590    @property
2591    def response_proto(self) -> cp_model_pb2.CpSolverResponse:
2592        """Returns the response object."""
2593        return self._checked_response.response()
2594
2595    def response_stats(self) -> str:
2596        """Returns some statistics on the solution found as a string."""
2597        return self._checked_response.response_stats()
2598
2599    def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]:
2600        """Returns the indices of the infeasible assumptions."""
2601        return self._checked_response.sufficient_assumptions_for_infeasibility()
2602
2603    def status_name(self, status: Optional[Any] = None) -> str:
2604        """Returns the name of the status returned by solve()."""
2605        if status is None:
2606            status = self._checked_response.status()
2607        return cp_model_pb2.CpSolverStatus.Name(status)
2608
2609    def solution_info(self) -> str:
2610        """Returns some information on the solve process.
2611
2612        Returns some information on how the solution was found, or the reason
2613        why the model or the parameters are invalid.
2614
2615        Raises:
2616          RuntimeError: if solve() has not been called.
2617        """
2618        return self._checked_response.solution_info()
2619
2620    @property
2621    def _checked_response(self) -> cmh.ResponseWrapper:
2622        """Checks solve() has been called, and returns a response wrapper."""
2623        if self.__response_wrapper is None:
2624            raise RuntimeError("solve() has not been called.")
2625        return self.__response_wrapper
2626
2627    # Compatibility with pre PEP8
2628    # pylint: disable=invalid-name
2629
2630    def BestObjectiveBound(self) -> float:
2631        return self.best_objective_bound
2632
2633    def BooleanValue(self, literal: LiteralT) -> bool:
2634        return self.boolean_value(literal)
2635
2636    def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series:
2637        return self.boolean_values(variables)
2638
2639    def NumBooleans(self) -> int:
2640        return self.num_booleans
2641
2642    def NumConflicts(self) -> int:
2643        return self.num_conflicts
2644
2645    def NumBranches(self) -> int:
2646        return self.num_branches
2647
2648    def ObjectiveValue(self) -> float:
2649        return self.objective_value
2650
2651    def ResponseProto(self) -> cp_model_pb2.CpSolverResponse:
2652        return self.response_proto
2653
2654    def ResponseStats(self) -> str:
2655        return self.response_stats()
2656
2657    def Solve(
2658        self,
2659        model: CpModel,
2660        solution_callback: Optional["CpSolverSolutionCallback"] = None,
2661    ) -> cp_model_pb2.CpSolverStatus:
2662        return self.solve(model, solution_callback)
2663
2664    def SolutionInfo(self) -> str:
2665        return self.solution_info()
2666
2667    def StatusName(self, status: Optional[Any] = None) -> str:
2668        return self.status_name(status)
2669
2670    def StopSearch(self) -> None:
2671        self.stop_search()
2672
2673    def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]:
2674        return self.sufficient_assumptions_for_infeasibility()
2675
2676    def UserTime(self) -> float:
2677        return self.user_time
2678
2679    def Value(self, expression: LinearExprT) -> int:
2680        return self.value(expression)
2681
2682    def Values(self, variables: _IndexOrSeries) -> pd.Series:
2683        return self.values(variables)
2684
2685    def WallTime(self) -> float:
2686        return self.wall_time
2687
2688    def SolveWithSolutionCallback(
2689        self, model: CpModel, callback: "CpSolverSolutionCallback"
2690    ) -> cp_model_pb2.CpSolverStatus:
2691        """DEPRECATED Use solve() with the callback argument."""
2692        warnings.warn(
2693            "solve_with_solution_callback is deprecated; use solve() with"
2694            + "the callback argument.",
2695            DeprecationWarning,
2696        )
2697        return self.solve(model, callback)
2698
2699    def SearchForAllSolutions(
2700        self, model: CpModel, callback: "CpSolverSolutionCallback"
2701    ) -> cp_model_pb2.CpSolverStatus:
2702        """DEPRECATED Use solve() with the right parameter.
2703
2704        Search for all solutions of a satisfiability problem.
2705
2706        This method searches for all feasible solutions of a given model.
2707        Then it feeds the solution to the callback.
2708
2709        Note that the model cannot contain an objective.
2710
2711        Args:
2712          model: The model to solve.
2713          callback: The callback that will be called at each solution.
2714
2715        Returns:
2716          The status of the solve:
2717
2718          * *FEASIBLE* if some solutions have been found
2719          * *INFEASIBLE* if the solver has proved there are no solution
2720          * *OPTIMAL* if all solutions have been found
2721        """
2722        warnings.warn(
2723            "search_for_all_solutions is deprecated; use solve() with"
2724            + "enumerate_all_solutions = True.",
2725            DeprecationWarning,
2726        )
2727        if model.has_objective():
2728            raise TypeError(
2729                "Search for all solutions is only defined on satisfiability problems"
2730            )
2731        # Store old parameter.
2732        enumerate_all = self.parameters.enumerate_all_solutions
2733        self.parameters.enumerate_all_solutions = True
2734
2735        status: cp_model_pb2.CpSolverStatus = self.solve(model, callback)
2736
2737        # Restore parameter.
2738        self.parameters.enumerate_all_solutions = enumerate_all
2739        return status
2740
2741
2742# pylint: enable=invalid-name
2743
2744
2745class CpSolverSolutionCallback(cmh.SolutionCallback):
2746    """Solution callback.
2747
2748    This class implements a callback that will be called at each new solution
2749    found during search.
2750
2751    The method on_solution_callback() will be called by the solver, and must be
2752    implemented. The current solution can be queried using the boolean_value()
2753    and value() methods.
2754
2755    These methods returns the same information as their counterpart in the
2756    `CpSolver` class.
2757    """
2758
2759    def __init__(self) -> None:
2760        cmh.SolutionCallback.__init__(self)
2761
2762    def OnSolutionCallback(self) -> None:
2763        """Proxy for the same method in snake case."""
2764        self.on_solution_callback()
2765
2766    def boolean_value(self, lit: LiteralT) -> bool:
2767        """Returns the boolean value of a boolean literal.
2768
2769        Args:
2770            lit: A boolean variable or its negation.
2771
2772        Returns:
2773            The Boolean value of the literal in the solution.
2774
2775        Raises:
2776            RuntimeError: if `lit` is not a boolean variable or its negation.
2777        """
2778        if not self.has_response():
2779            raise RuntimeError("solve() has not been called.")
2780        return self.BooleanValue(lit)
2781
2782    def value(self, expression: LinearExprT) -> int:
2783        """Evaluates an linear expression in the current solution.
2784
2785        Args:
2786            expression: a linear expression of the model.
2787
2788        Returns:
2789            An integer value equal to the evaluation of the linear expression
2790            against the current solution.
2791
2792        Raises:
2793            RuntimeError: if 'expression' is not a LinearExpr.
2794        """
2795        if not self.has_response():
2796            raise RuntimeError("solve() has not been called.")
2797        return self.Value(expression)
2798
2799    def has_response(self) -> bool:
2800        return self.HasResponse()
2801
2802    def stop_search(self) -> None:
2803        """Stops the current search asynchronously."""
2804        if not self.has_response():
2805            raise RuntimeError("solve() has not been called.")
2806        self.StopSearch()
2807
2808    @property
2809    def objective_value(self) -> float:
2810        """Returns the value of the objective after solve."""
2811        if not self.has_response():
2812            raise RuntimeError("solve() has not been called.")
2813        return self.ObjectiveValue()
2814
2815    @property
2816    def best_objective_bound(self) -> float:
2817        """Returns the best lower (upper) bound found when min(max)imizing."""
2818        if not self.has_response():
2819            raise RuntimeError("solve() has not been called.")
2820        return self.BestObjectiveBound()
2821
2822    @property
2823    def num_booleans(self) -> int:
2824        """Returns the number of boolean variables managed by the SAT solver."""
2825        if not self.has_response():
2826            raise RuntimeError("solve() has not been called.")
2827        return self.NumBooleans()
2828
2829    @property
2830    def num_conflicts(self) -> int:
2831        """Returns the number of conflicts since the creation of the solver."""
2832        if not self.has_response():
2833            raise RuntimeError("solve() has not been called.")
2834        return self.NumConflicts()
2835
2836    @property
2837    def num_branches(self) -> int:
2838        """Returns the number of search branches explored by the solver."""
2839        if not self.has_response():
2840            raise RuntimeError("solve() has not been called.")
2841        return self.NumBranches()
2842
2843    @property
2844    def num_integer_propagations(self) -> int:
2845        """Returns the number of integer propagations done by the solver."""
2846        if not self.has_response():
2847            raise RuntimeError("solve() has not been called.")
2848        return self.NumIntegerPropagations()
2849
2850    @property
2851    def num_boolean_propagations(self) -> int:
2852        """Returns the number of Boolean propagations done by the solver."""
2853        if not self.has_response():
2854            raise RuntimeError("solve() has not been called.")
2855        return self.NumBooleanPropagations()
2856
2857    @property
2858    def deterministic_time(self) -> float:
2859        """Returns the determistic time in seconds since the creation of the solver."""
2860        if not self.has_response():
2861            raise RuntimeError("solve() has not been called.")
2862        return self.DeterministicTime()
2863
2864    @property
2865    def wall_time(self) -> float:
2866        """Returns the wall time in seconds since the creation of the solver."""
2867        if not self.has_response():
2868            raise RuntimeError("solve() has not been called.")
2869        return self.WallTime()
2870
2871    @property
2872    def user_time(self) -> float:
2873        """Returns the user time in seconds since the creation of the solver."""
2874        if not self.has_response():
2875            raise RuntimeError("solve() has not been called.")
2876        return self.UserTime()
2877
2878    @property
2879    def response_proto(self) -> cp_model_pb2.CpSolverResponse:
2880        """Returns the response object."""
2881        if not self.has_response():
2882            raise RuntimeError("solve() has not been called.")
2883        return self.Response()
2884
2885
2886class ObjectiveSolutionPrinter(CpSolverSolutionCallback):
2887    """Display the objective value and time of intermediate solutions."""
2888
2889    def __init__(self) -> None:
2890        CpSolverSolutionCallback.__init__(self)
2891        self.__solution_count = 0
2892        self.__start_time = time.time()
2893
2894    def on_solution_callback(self) -> None:
2895        """Called on each new solution."""
2896        current_time = time.time()
2897        obj = self.objective_value
2898        print(
2899            f"Solution {self.__solution_count}, time ="
2900            f" {current_time - self.__start_time:0.2f} s, objective = {obj}",
2901            flush=True,
2902        )
2903        self.__solution_count += 1
2904
2905    def solution_count(self) -> int:
2906        """Returns the number of solutions found."""
2907        return self.__solution_count
2908
2909
2910class VarArrayAndObjectiveSolutionPrinter(CpSolverSolutionCallback):
2911    """Print intermediate solutions (objective, variable values, time)."""
2912
2913    def __init__(self, variables: Sequence[IntVar]) -> None:
2914        CpSolverSolutionCallback.__init__(self)
2915        self.__variables: Sequence[IntVar] = variables
2916        self.__solution_count: int = 0
2917        self.__start_time: float = time.time()
2918
2919    def on_solution_callback(self) -> None:
2920        """Called on each new solution."""
2921        current_time = time.time()
2922        obj = self.objective_value
2923        print(
2924            f"Solution {self.__solution_count}, time ="
2925            f" {current_time - self.__start_time:0.2f} s, objective = {obj}"
2926        )
2927        for v in self.__variables:
2928            print(f"  {v} = {self.value(v)}", end=" ")
2929        print(flush=True)
2930        self.__solution_count += 1
2931
2932    @property
2933    def solution_count(self) -> int:
2934        """Returns the number of solutions found."""
2935        return self.__solution_count
2936
2937
2938class VarArraySolutionPrinter(CpSolverSolutionCallback):
2939    """Print intermediate solutions (variable values, time)."""
2940
2941    def __init__(self, variables: Sequence[IntVar]) -> None:
2942        CpSolverSolutionCallback.__init__(self)
2943        self.__variables: Sequence[IntVar] = variables
2944        self.__solution_count: int = 0
2945        self.__start_time: float = time.time()
2946
2947    def on_solution_callback(self) -> None:
2948        """Called on each new solution."""
2949        current_time = time.time()
2950        print(
2951            f"Solution {self.__solution_count}, time ="
2952            f" {current_time - self.__start_time:0.2f} s"
2953        )
2954        for v in self.__variables:
2955            print(f"  {v} = {self.value(v)}", end=" ")
2956        print(flush=True)
2957        self.__solution_count += 1
2958
2959    @property
2960    def solution_count(self) -> int:
2961        """Returns the number of solutions found."""
2962        return self.__solution_count
2963
2964
2965def _get_index(obj: _IndexOrSeries) -> pd.Index:
2966    """Returns the indices of `obj` as a `pd.Index`."""
2967    if isinstance(obj, pd.Series):
2968        return obj.index
2969    return obj
2970
2971
2972def _convert_to_integral_series_and_validate_index(
2973    value_or_series: Union[IntegralT, pd.Series], index: pd.Index
2974) -> pd.Series:
2975    """Returns a pd.Series of the given index with the corresponding values.
2976
2977    Args:
2978      value_or_series: the values to be converted (if applicable).
2979      index: the index of the resulting pd.Series.
2980
2981    Returns:
2982      pd.Series: The set of values with the given index.
2983
2984    Raises:
2985      TypeError: If the type of `value_or_series` is not recognized.
2986      ValueError: If the index does not match.
2987    """
2988    if isinstance(value_or_series, IntegralTypes):
2989        return pd.Series(data=value_or_series, index=index)
2990    elif isinstance(value_or_series, pd.Series):
2991        if value_or_series.index.equals(index):
2992            return value_or_series
2993        else:
2994            raise ValueError("index does not match")
2995    else:
2996        raise TypeError(f"invalid type={type(value_or_series).__name__!r}")
2997
2998
2999def _convert_to_linear_expr_series_and_validate_index(
3000    value_or_series: Union[LinearExprT, pd.Series], index: pd.Index
3001) -> pd.Series:
3002    """Returns a pd.Series of the given index with the corresponding values.
3003
3004    Args:
3005      value_or_series: the values to be converted (if applicable).
3006      index: the index of the resulting pd.Series.
3007
3008    Returns:
3009      pd.Series: The set of values with the given index.
3010
3011    Raises:
3012      TypeError: If the type of `value_or_series` is not recognized.
3013      ValueError: If the index does not match.
3014    """
3015    if isinstance(value_or_series, IntegralTypes):
3016        return pd.Series(data=value_or_series, index=index)
3017    elif isinstance(value_or_series, pd.Series):
3018        if value_or_series.index.equals(index):
3019            return value_or_series
3020        else:
3021            raise ValueError("index does not match")
3022    else:
3023        raise TypeError(f"invalid type={type(value_or_series).__name__!r}")
3024
3025
3026def _convert_to_literal_series_and_validate_index(
3027    value_or_series: Union[LiteralT, pd.Series], index: pd.Index
3028) -> pd.Series:
3029    """Returns a pd.Series of the given index with the corresponding values.
3030
3031    Args:
3032      value_or_series: the values to be converted (if applicable).
3033      index: the index of the resulting pd.Series.
3034
3035    Returns:
3036      pd.Series: The set of values with the given index.
3037
3038    Raises:
3039      TypeError: If the type of `value_or_series` is not recognized.
3040      ValueError: If the index does not match.
3041    """
3042    if isinstance(value_or_series, IntegralTypes):
3043        return pd.Series(data=value_or_series, index=index)
3044    elif isinstance(value_or_series, pd.Series):
3045        if value_or_series.index.equals(index):
3046            return value_or_series
3047        else:
3048            raise ValueError("index does not match")
3049    else:
3050        raise TypeError(f"invalid type={type(value_or_series).__name__!r}")
class Domain(pybind11_builtins.pybind11_object):

We call domain any subset of Int64 = [kint64min, kint64max].

This class can be used to represent such set efficiently as a sorted and non-adjacent list of intervals. This is efficient as long as the size of such list stays reasonable.

In the comments below, the domain of *this will always be written 'D'. Note that all the functions are safe with respect to integer overflow.

Domain()

__init__(self: ortools.util.python.sorted_interval_list.Domain, arg0: int, arg1: int) -> None

By default, Domain will be empty.

def all_values(unknown):

all_values() -> ortools.util.python.sorted_interval_list.Domain

Returns the full domain Int64.

def from_values(unknown):

from_values(values: list[int]) -> ortools.util.python.sorted_interval_list.Domain

Creates a domain from the union of an unsorted list of integer values. Input values may be repeated, with no consequence on the output

def from_intervals(unknown):

from_intervals(intervals: list[list[int]]) -> ortools.util.python.sorted_interval_list.Domain

This method is available in Python, Java and .NET. It allows building a Domain object from a list of intervals (long[][] in Java and .NET, [[0, 2], [5, 5], [8, 10]] in python).

def from_flat_intervals(unknown):

from_flat_intervals(flat_intervals: list[int]) -> ortools.util.python.sorted_interval_list.Domain

This method is available in Python, Java and .NET. It allows building a Domain object from a flattened list of intervals (long[] in Java and .NET, [0, 2, 5, 5, 8, 10] in python).

def addition_with(unknown):

addition_with(self: ortools.util.python.sorted_interval_list.Domain, domain: ortools.util.python.sorted_interval_list.Domain) -> ortools.util.python.sorted_interval_list.Domain

Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.

def complement(unknown):
def contains(unknown):

contains(self: ortools.util.python.sorted_interval_list.Domain, value: int) -> bool

Returns true iff value is in Domain.

def flattened_intervals(unknown):

flattened_intervals(self: ortools.util.python.sorted_interval_list.Domain) -> list[int]

This method returns the flattened list of interval bounds of the domain.

Thus the domain {0, 1, 2, 5, 8, 9, 10} will return [0, 2, 5, 5, 8, 10] (as a C++ std::vector, as a java or C# long[], as a python list of integers).

def intersection_with(unknown):
def is_empty(unknown):

is_empty(self: ortools.util.python.sorted_interval_list.Domain) -> bool

Returns true if this is the empty set.

def size(unknown):

size(self: ortools.util.python.sorted_interval_list.Domain) -> int

Returns the number of elements in the domain. It is capped at kint64max

def max(unknown):

max(self: ortools.util.python.sorted_interval_list.Domain) -> int

Returns the max value of the domain. The domain must not be empty.

def min(unknown):

min(self: ortools.util.python.sorted_interval_list.Domain) -> int

Returns the min value of the domain. The domain must not be empty.

def negation(unknown):

negation(self: ortools.util.python.sorted_interval_list.Domain) -> ortools.util.python.sorted_interval_list.Domain

Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.

Note in particular that if the negation of Int64 is not Int64 but Int64 \ {kint64min} !!

def union_with(unknown):
def AllValues(unknown):

AllValues() -> ortools.util.python.sorted_interval_list.Domain

Returns the full domain Int64.

def FromValues(unknown):

FromValues(values: list[int]) -> ortools.util.python.sorted_interval_list.Domain

Creates a domain from the union of an unsorted list of integer values. Input values may be repeated, with no consequence on the output

def FromIntervals(unknown):

FromIntervals(intervals: list[list[int]]) -> ortools.util.python.sorted_interval_list.Domain

This method is available in Python, Java and .NET. It allows building a Domain object from a list of intervals (long[][] in Java and .NET, [[0, 2], [5, 5], [8, 10]] in python).

def FromFlatIntervals(unknown):

FromFlatIntervals(flat_intervals: list[int]) -> ortools.util.python.sorted_interval_list.Domain

This method is available in Python, Java and .NET. It allows building a Domain object from a flattened list of intervals (long[] in Java and .NET, [0, 2, 5, 5, 8, 10] in python).

def FlattenedIntervals(unknown):

FlattenedIntervals(self: ortools.util.python.sorted_interval_list.Domain) -> list[int]

This method returns the flattened list of interval bounds of the domain.

Thus the domain {0, 1, 2, 5, 8, 9, 10} will return [0, 2, 5, 5, 8, 10] (as a C++ std::vector, as a java or C# long[], as a python list of integers).

class BoundedLinearExpression(pybind11_builtins.pybind11_object):

A class to hold a linear expression with bounds.

A flattened and optimized floating point linear expression.

It can be used to cache complex expressions as parsing them is only done once.

vars

(arg0: ortools.sat.python.cp_model_helper.FlatFloatExpr) -> list[operations_research::sat::python::BaseIntVar]

coeffs

A flattened and optimized integer linear expression.

It can be used to cache complex expressions as parsing them is only done once.

vars

(arg0: ortools.sat.python.cp_model_helper.FlatIntExpr) -> list[operations_research::sat::python::BaseIntVar]

class LinearExpr(pybind11_builtins.pybind11_object):

A class to hold an integer or floating point linear expression.

A linear expression is built from (integer or floating point) constants and variables. For example, x + 2 * (y - z + 1).

Linear expressions are used in CP-SAT models in constraints and in the objective.

Note that constraints only accept linear expressions with integral coefficients and constants. On the other hand, The objective can be a linear expression with floating point coefficients and constants.

You can define linear constraints as in:

model.add(x + 2 * y <= 5)
model.add(sum(array_of_vars) == 5)
  • In CP-SAT, the objective is a linear expression:
model.minimize(x + 2 * y + z)
  • For large arrays, using the LinearExpr class is faster that using the python sum() function. You can create constraints and the objective from lists of linear expressions or coefficients as follows:
model.minimize(cp_model.LinearExpr.sum(expressions))
model.add(cp_model.LinearExpr.weighted_sum(expressions, coefficients) >= 0)
LinearExpr(*args, **kwargs)
def sum(unknown):

sum(*args) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns the sum(expressions).

def weighted_sum(unknown):

weighted_sum(expressions: Sequence, coefficients: Sequence) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns the sum of (expressions[i] * coefficients[i])

def term(unknown):

term(args, *kwargs) Overloaded function.

  1. term(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: int) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns expr * coeff.

  1. term(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: float) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns expr * coeff.

def affine(unknown):

affine(args, *kwargs) Overloaded function.

  1. affine(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: int, offset: int) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns expr * coeff + offset.

  1. affine(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: float, offset: float) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns expr * coeff + offset.

def constant(unknown):

constant(args, *kwargs) Overloaded function.

  1. constant(value: int) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns a new LinearExpr that is the given constant.

  1. constant(value: float) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns a new LinearExpr that is the given constant.

def Sum(unknown):
def WeightedSum(unknown):

WeightedSum(expressions: Sequence, coefficients: Sequence) -> ortools.sat.python.cp_model_helper.LinearExpr

def Term(unknown):

Term(args, *kwargs) Overloaded function.

  1. Term(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: int) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns expr * coeff.

  1. Term(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: float) -> ortools.sat.python.cp_model_helper.LinearExpr

Returns expr * coeff.

def is_integer(unknown):
class NotBooleanVariable(ortools.sat.python.cp_model_helper.Literal):

A class to hold a negated variable index.

NotBooleanVariable(*args, **kwargs)
def negated(unknown):

negated(self: ortools.sat.python.cp_model_helper.NotBooleanVariable) -> ortools.sat.python.cp_model_helper.Literal

Returns the negation of the current literal, that is the original Boolean variable.

def Not(unknown):

Not(self: ortools.sat.python.cp_model_helper.NotBooleanVariable) -> ortools.sat.python.cp_model_helper.Literal

Returns the negation of the current literal, that is the original Boolean variable.

INT_MIN = -9223372036854775808
INT_MAX = 9223372036854775807
INT32_MIN = -2147483648
INT32_MAX = 2147483647
UNKNOWN = 0
MODEL_INVALID = 1
FEASIBLE = 2
INFEASIBLE = 3
OPTIMAL = 4
CHOOSE_FIRST = 0
CHOOSE_LOWEST_MIN = 1
CHOOSE_HIGHEST_MAX = 2
CHOOSE_MIN_DOMAIN_SIZE = 3
CHOOSE_MAX_DOMAIN_SIZE = 4
SELECT_MIN_VALUE = 0
SELECT_MAX_VALUE = 1
SELECT_LOWER_HALF = 2
SELECT_UPPER_HALF = 3
SELECT_MEDIAN_VALUE = 4
SELECT_RANDOM_HALF = 5
IntegralT = typing.Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]
IntegralTypes = (<class 'int'>, <class 'numpy.int8'>, <class 'numpy.uint8'>, <class 'numpy.int32'>, <class 'numpy.uint32'>, <class 'numpy.int64'>, <class 'numpy.uint64'>)
NumberT = typing.Union[int, float, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, numpy.float64]
NumberTypes = (<class 'int'>, <class 'float'>, <class 'numpy.int8'>, <class 'numpy.uint8'>, <class 'numpy.int32'>, <class 'numpy.uint32'>, <class 'numpy.int64'>, <class 'numpy.uint64'>, <class 'numpy.float64'>)
LiteralT = typing.Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]
VariableT = typing.Union[ForwardRef('IntVar'), int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]
LinearExprT = typing.Union[LinearExpr, ForwardRef('IntVar'), int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]
ObjLinearExprT = typing.Union[LinearExpr, int, float, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, numpy.float64]
ArcT = typing.Tuple[typing.Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], typing.Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], typing.Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]]
def display_bounds(bounds: Sequence[int]) -> str:
173def display_bounds(bounds: Sequence[int]) -> str:
174    """Displays a flattened list of intervals."""
175    out = ""
176    for i in range(0, len(bounds), 2):
177        if i != 0:
178            out += ", "
179        if bounds[i] == bounds[i + 1]:
180            out += str(bounds[i])
181        else:
182            out += str(bounds[i]) + ".." + str(bounds[i + 1])
183    return out

Displays a flattened list of intervals.

def short_name(model: ortools.sat.cp_model_pb2.CpModelProto, i: int) -> str:
186def short_name(model: cp_model_pb2.CpModelProto, i: int) -> str:
187    """Returns a short name of an integer variable, or its negation."""
188    if i < 0:
189        return f"not({short_name(model, -i - 1)})"
190    v = model.variables[i]
191    if v.name:
192        return v.name
193    elif len(v.domain) == 2 and v.domain[0] == v.domain[1]:
194        return str(v.domain[0])
195    else:
196        return f"[{display_bounds(v.domain)}]"

Returns a short name of an integer variable, or its negation.

def short_expr_name( model: ortools.sat.cp_model_pb2.CpModelProto, e: ortools.sat.cp_model_pb2.LinearExpressionProto) -> str:
199def short_expr_name(
200    model: cp_model_pb2.CpModelProto, e: cp_model_pb2.LinearExpressionProto
201) -> str:
202    """Pretty-print LinearExpressionProto instances."""
203    if not e.vars:
204        return str(e.offset)
205    if len(e.vars) == 1:
206        var_name = short_name(model, e.vars[0])
207        coeff = e.coeffs[0]
208        result = ""
209        if coeff == 1:
210            result = var_name
211        elif coeff == -1:
212            result = f"-{var_name}"
213        elif coeff != 0:
214            result = f"{coeff} * {var_name}"
215        if e.offset > 0:
216            result = f"{result} + {e.offset}"
217        elif e.offset < 0:
218            result = f"{result} - {-e.offset}"
219        return result
220    # TODO(user): Support more than affine expressions.
221    return str(e)

Pretty-print LinearExpressionProto instances.

224class IntVar(cmh.BaseIntVar):
225    """An integer variable.
226
227    An IntVar is an object that can take on any integer value within defined
228    ranges. Variables appear in constraint like:
229
230        x + y >= 5
231        AllDifferent([x, y, z])
232
233    Solving a model is equivalent to finding, for each variable, a single value
234    from the set of initial values (called the initial domain), such that the
235    model is feasible, or optimal if you provided an objective function.
236    """
237
238    def __init__(
239        self,
240        model: cp_model_pb2.CpModelProto,
241        domain: Union[int, sorted_interval_list.Domain],
242        is_boolean: bool,
243        name: Optional[str],
244    ) -> None:
245        """See CpModel.new_int_var below."""
246        self.__var: cp_model_pb2.IntegerVariableProto
247        # Python do not support multiple __init__ methods.
248        # This method is only called from the CpModel class.
249        # We hack the parameter to support the two cases:
250        # case 1:
251        #     model is a CpModelProto, domain is a Domain, and name is a string.
252        # case 2:
253        #     model is a CpModelProto, domain is an index (int), and name is None.
254        if isinstance(domain, IntegralTypes) and name is None:
255            cmh.BaseIntVar.__init__(self, int(domain), is_boolean)
256            self.__var = model.variables[domain]
257        else:
258            cmh.BaseIntVar.__init__(self, len(model.variables), is_boolean)
259            self.__var = model.variables.add()
260            self.__var.domain.extend(
261                cast(sorted_interval_list.Domain, domain).flattened_intervals()
262            )
263            if name is not None:
264                self.__var.name = name
265
266    @property
267    def proto(self) -> cp_model_pb2.IntegerVariableProto:
268        """Returns the variable protobuf."""
269        return self.__var
270
271    def is_equal_to(self, other: Any) -> bool:
272        """Returns true if self == other in the python sense."""
273        if not isinstance(other, IntVar):
274            return False
275        return self.index == other.index
276
277    def __str__(self) -> str:
278        if not self.__var.name:
279            if (
280                len(self.__var.domain) == 2
281                and self.__var.domain[0] == self.__var.domain[1]
282            ):
283                # Special case for constants.
284                return str(self.__var.domain[0])
285            elif self.is_boolean:
286                return f"BooleanVar({self.__index})"
287            else:
288                return f"IntVar({self.__index})"
289        else:
290            return self.__var.name
291
292    def __repr__(self) -> str:
293        return f"{self}({display_bounds(self.__var.domain)})"
294
295    @property
296    def name(self) -> str:
297        if not self.__var or not self.__var.name:
298            return ""
299        return self.__var.name
300
301    # Pre PEP8 compatibility.
302    # pylint: disable=invalid-name
303    def Name(self) -> str:
304        return self.name
305
306    def Proto(self) -> cp_model_pb2.IntegerVariableProto:
307        return self.proto
308
309    # pylint: enable=invalid-name

An integer variable.

An IntVar is an object that can take on any integer value within defined ranges. Variables appear in constraint like:

x + y >= 5
AllDifferent([x, y, z])

Solving a model is equivalent to finding, for each variable, a single value from the set of initial values (called the initial domain), such that the model is feasible, or optimal if you provided an objective function.

IntVar( model: ortools.sat.cp_model_pb2.CpModelProto, domain: Union[int, Domain], is_boolean: bool, name: Optional[str])
238    def __init__(
239        self,
240        model: cp_model_pb2.CpModelProto,
241        domain: Union[int, sorted_interval_list.Domain],
242        is_boolean: bool,
243        name: Optional[str],
244    ) -> None:
245        """See CpModel.new_int_var below."""
246        self.__var: cp_model_pb2.IntegerVariableProto
247        # Python do not support multiple __init__ methods.
248        # This method is only called from the CpModel class.
249        # We hack the parameter to support the two cases:
250        # case 1:
251        #     model is a CpModelProto, domain is a Domain, and name is a string.
252        # case 2:
253        #     model is a CpModelProto, domain is an index (int), and name is None.
254        if isinstance(domain, IntegralTypes) and name is None:
255            cmh.BaseIntVar.__init__(self, int(domain), is_boolean)
256            self.__var = model.variables[domain]
257        else:
258            cmh.BaseIntVar.__init__(self, len(model.variables), is_boolean)
259            self.__var = model.variables.add()
260            self.__var.domain.extend(
261                cast(sorted_interval_list.Domain, domain).flattened_intervals()
262            )
263            if name is not None:
264                self.__var.name = name
266    @property
267    def proto(self) -> cp_model_pb2.IntegerVariableProto:
268        """Returns the variable protobuf."""
269        return self.__var

Returns the variable protobuf.

def is_equal_to(self, other: Any) -> bool:
271    def is_equal_to(self, other: Any) -> bool:
272        """Returns true if self == other in the python sense."""
273        if not isinstance(other, IntVar):
274            return False
275        return self.index == other.index

Returns true if self == other in the python sense.

name: str
295    @property
296    def name(self) -> str:
297        if not self.__var or not self.__var.name:
298            return ""
299        return self.__var.name
def Name(self) -> str:
303    def Name(self) -> str:
304        return self.name
def Proto(self) -> ortools.sat.cp_model_pb2.IntegerVariableProto:
306    def Proto(self) -> cp_model_pb2.IntegerVariableProto:
307        return self.proto
class Constraint:
312class Constraint:
313    """Base class for constraints.
314
315    Constraints are built by the CpModel through the add<XXX> methods.
316    Once created by the CpModel class, they are automatically added to the model.
317    The purpose of this class is to allow specification of enforcement literals
318    for this constraint.
319
320        b = model.new_bool_var('b')
321        x = model.new_int_var(0, 10, 'x')
322        y = model.new_int_var(0, 10, 'y')
323
324        model.add(x + 2 * y == 5).only_enforce_if(b.negated())
325    """
326
327    def __init__(
328        self,
329        cp_model: "CpModel",
330    ) -> None:
331        self.__index: int = len(cp_model.proto.constraints)
332        self.__cp_model: "CpModel" = cp_model
333        self.__constraint: cp_model_pb2.ConstraintProto = (
334            cp_model.proto.constraints.add()
335        )
336
337    @overload
338    def only_enforce_if(self, boolvar: Iterable[LiteralT]) -> "Constraint": ...
339
340    @overload
341    def only_enforce_if(self, *boolvar: LiteralT) -> "Constraint": ...
342
343    def only_enforce_if(self, *boolvar) -> "Constraint":
344        """Adds an enforcement literal to the constraint.
345
346        This method adds one or more literals (that is, a boolean variable or its
347        negation) as enforcement literals. The conjunction of all these literals
348        determines whether the constraint is active or not. It acts as an
349        implication, so if the conjunction is true, it implies that the constraint
350        must be enforced. If it is false, then the constraint is ignored.
351
352        BoolOr, BoolAnd, and linear constraints all support enforcement literals.
353
354        Args:
355          *boolvar: One or more Boolean literals.
356
357        Returns:
358          self.
359        """
360        for lit in expand_generator_or_tuple(boolvar):
361            if (cmn.is_boolean(lit) and lit) or (
362                isinstance(lit, IntegralTypes) and lit == 1
363            ):
364                # Always true. Do nothing.
365                pass
366            elif (cmn.is_boolean(lit) and not lit) or (
367                isinstance(lit, IntegralTypes) and lit == 0
368            ):
369                self.__constraint.enforcement_literal.append(
370                    self.__cp_model.new_constant(0).index
371                )
372            else:
373                self.__constraint.enforcement_literal.append(
374                    cast(cmh.Literal, lit).index
375                )
376        return self
377
378    def with_name(self, name: str) -> "Constraint":
379        """Sets the name of the constraint."""
380        if name:
381            self.__constraint.name = name
382        else:
383            self.__constraint.ClearField("name")
384        return self
385
386    @property
387    def name(self) -> str:
388        """Returns the name of the constraint."""
389        if not self.__constraint or not self.__constraint.name:
390            return ""
391        return self.__constraint.name
392
393    @property
394    def index(self) -> int:
395        """Returns the index of the constraint in the model."""
396        return self.__index
397
398    @property
399    def proto(self) -> cp_model_pb2.ConstraintProto:
400        """Returns the constraint protobuf."""
401        return self.__constraint
402
403    # Pre PEP8 compatibility.
404    # pylint: disable=invalid-name
405    OnlyEnforceIf = only_enforce_if
406    WithName = with_name
407
408    def Name(self) -> str:
409        return self.name
410
411    def Index(self) -> int:
412        return self.index
413
414    def Proto(self) -> cp_model_pb2.ConstraintProto:
415        return self.proto
416
417    # pylint: enable=invalid-name

Base class for constraints.

Constraints are built by the CpModel through the add methods. Once created by the CpModel class, they are automatically added to the model. The purpose of this class is to allow specification of enforcement literals for this constraint.

b = model.new_bool_var('b')
x = model.new_int_var(0, 10, 'x')
y = model.new_int_var(0, 10, 'y')

model.add(x + 2 * y == 5).only_enforce_if(b.negated())
Constraint(cp_model: CpModel)
327    def __init__(
328        self,
329        cp_model: "CpModel",
330    ) -> None:
331        self.__index: int = len(cp_model.proto.constraints)
332        self.__cp_model: "CpModel" = cp_model
333        self.__constraint: cp_model_pb2.ConstraintProto = (
334            cp_model.proto.constraints.add()
335        )
def only_enforce_if(self, *boolvar) -> Constraint:
343    def only_enforce_if(self, *boolvar) -> "Constraint":
344        """Adds an enforcement literal to the constraint.
345
346        This method adds one or more literals (that is, a boolean variable or its
347        negation) as enforcement literals. The conjunction of all these literals
348        determines whether the constraint is active or not. It acts as an
349        implication, so if the conjunction is true, it implies that the constraint
350        must be enforced. If it is false, then the constraint is ignored.
351
352        BoolOr, BoolAnd, and linear constraints all support enforcement literals.
353
354        Args:
355          *boolvar: One or more Boolean literals.
356
357        Returns:
358          self.
359        """
360        for lit in expand_generator_or_tuple(boolvar):
361            if (cmn.is_boolean(lit) and lit) or (
362                isinstance(lit, IntegralTypes) and lit == 1
363            ):
364                # Always true. Do nothing.
365                pass
366            elif (cmn.is_boolean(lit) and not lit) or (
367                isinstance(lit, IntegralTypes) and lit == 0
368            ):
369                self.__constraint.enforcement_literal.append(
370                    self.__cp_model.new_constant(0).index
371                )
372            else:
373                self.__constraint.enforcement_literal.append(
374                    cast(cmh.Literal, lit).index
375                )
376        return self

Adds an enforcement literal to the constraint.

This method adds one or more literals (that is, a boolean variable or its negation) as enforcement literals. The conjunction of all these literals determines whether the constraint is active or not. It acts as an implication, so if the conjunction is true, it implies that the constraint must be enforced. If it is false, then the constraint is ignored.

BoolOr, BoolAnd, and linear constraints all support enforcement literals.

Arguments:
  • *boolvar: One or more Boolean literals.
Returns:

self.

def with_name(self, name: str) -> Constraint:
378    def with_name(self, name: str) -> "Constraint":
379        """Sets the name of the constraint."""
380        if name:
381            self.__constraint.name = name
382        else:
383            self.__constraint.ClearField("name")
384        return self

Sets the name of the constraint.

name: str
386    @property
387    def name(self) -> str:
388        """Returns the name of the constraint."""
389        if not self.__constraint or not self.__constraint.name:
390            return ""
391        return self.__constraint.name

Returns the name of the constraint.

index: int
393    @property
394    def index(self) -> int:
395        """Returns the index of the constraint in the model."""
396        return self.__index

Returns the index of the constraint in the model.

398    @property
399    def proto(self) -> cp_model_pb2.ConstraintProto:
400        """Returns the constraint protobuf."""
401        return self.__constraint

Returns the constraint protobuf.

def OnlyEnforceIf(self, *boolvar) -> Constraint:
343    def only_enforce_if(self, *boolvar) -> "Constraint":
344        """Adds an enforcement literal to the constraint.
345
346        This method adds one or more literals (that is, a boolean variable or its
347        negation) as enforcement literals. The conjunction of all these literals
348        determines whether the constraint is active or not. It acts as an
349        implication, so if the conjunction is true, it implies that the constraint
350        must be enforced. If it is false, then the constraint is ignored.
351
352        BoolOr, BoolAnd, and linear constraints all support enforcement literals.
353
354        Args:
355          *boolvar: One or more Boolean literals.
356
357        Returns:
358          self.
359        """
360        for lit in expand_generator_or_tuple(boolvar):
361            if (cmn.is_boolean(lit) and lit) or (
362                isinstance(lit, IntegralTypes) and lit == 1
363            ):
364                # Always true. Do nothing.
365                pass
366            elif (cmn.is_boolean(lit) and not lit) or (
367                isinstance(lit, IntegralTypes) and lit == 0
368            ):
369                self.__constraint.enforcement_literal.append(
370                    self.__cp_model.new_constant(0).index
371                )
372            else:
373                self.__constraint.enforcement_literal.append(
374                    cast(cmh.Literal, lit).index
375                )
376        return self

Adds an enforcement literal to the constraint.

This method adds one or more literals (that is, a boolean variable or its negation) as enforcement literals. The conjunction of all these literals determines whether the constraint is active or not. It acts as an implication, so if the conjunction is true, it implies that the constraint must be enforced. If it is false, then the constraint is ignored.

BoolOr, BoolAnd, and linear constraints all support enforcement literals.

Arguments:
  • *boolvar: One or more Boolean literals.
Returns:

self.

def WithName(self, name: str) -> Constraint:
378    def with_name(self, name: str) -> "Constraint":
379        """Sets the name of the constraint."""
380        if name:
381            self.__constraint.name = name
382        else:
383            self.__constraint.ClearField("name")
384        return self

Sets the name of the constraint.

def Name(self) -> str:
408    def Name(self) -> str:
409        return self.name
def Index(self) -> int:
411    def Index(self) -> int:
412        return self.index
def Proto(self) -> ortools.sat.cp_model_pb2.ConstraintProto:
414    def Proto(self) -> cp_model_pb2.ConstraintProto:
415        return self.proto
class VariableList:
420class VariableList:
421    """Stores all integer variables of the model."""
422
423    def __init__(self) -> None:
424        self.__var_list: list[IntVar] = []
425
426    def append(self, var: IntVar) -> None:
427        assert var.index == len(self.__var_list)
428        self.__var_list.append(var)
429
430    def get(self, index: int) -> IntVar:
431        if index < 0 or index >= len(self.__var_list):
432            raise ValueError("Index out of bounds.")
433        return self.__var_list[index]
434
435    def rebuild_expr(
436        self,
437        proto: cp_model_pb2.LinearExpressionProto,
438    ) -> LinearExprT:
439        """Recreate a LinearExpr from a LinearExpressionProto."""
440        num_elements = len(proto.vars)
441        if num_elements == 0:
442            return proto.offset
443        elif num_elements == 1:
444            var = self.get(proto.vars[0])
445            return LinearExpr.affine(
446                var, proto.coeffs[0], proto.offset
447            )  # pytype: disable=bad-return-type
448        else:
449            variables = []
450            for var_index in range(len(proto.vars)):
451                var = self.get(var_index)
452                variables.append(var)
453            if proto.offset != 0:
454                coeffs = []
455                coeffs.extend(proto.coeffs)
456                coeffs.append(1)
457                variables.append(proto.offset)
458                return LinearExpr.weighted_sum(variables, coeffs)
459            else:
460                return LinearExpr.weighted_sum(variables, proto.coeffs)

Stores all integer variables of the model.

def append(self, var: IntVar) -> None:
426    def append(self, var: IntVar) -> None:
427        assert var.index == len(self.__var_list)
428        self.__var_list.append(var)
def get(self, index: int) -> IntVar:
430    def get(self, index: int) -> IntVar:
431        if index < 0 or index >= len(self.__var_list):
432            raise ValueError("Index out of bounds.")
433        return self.__var_list[index]
def rebuild_expr( self, proto: ortools.sat.cp_model_pb2.LinearExpressionProto) -> Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]:
435    def rebuild_expr(
436        self,
437        proto: cp_model_pb2.LinearExpressionProto,
438    ) -> LinearExprT:
439        """Recreate a LinearExpr from a LinearExpressionProto."""
440        num_elements = len(proto.vars)
441        if num_elements == 0:
442            return proto.offset
443        elif num_elements == 1:
444            var = self.get(proto.vars[0])
445            return LinearExpr.affine(
446                var, proto.coeffs[0], proto.offset
447            )  # pytype: disable=bad-return-type
448        else:
449            variables = []
450            for var_index in range(len(proto.vars)):
451                var = self.get(var_index)
452                variables.append(var)
453            if proto.offset != 0:
454                coeffs = []
455                coeffs.extend(proto.coeffs)
456                coeffs.append(1)
457                variables.append(proto.offset)
458                return LinearExpr.weighted_sum(variables, coeffs)
459            else:
460                return LinearExpr.weighted_sum(variables, proto.coeffs)

Recreate a LinearExpr from a LinearExpressionProto.

class IntervalVar:
463class IntervalVar:
464    """Represents an Interval variable.
465
466    An interval variable is both a constraint and a variable. It is defined by
467    three integer variables: start, size, and end.
468
469    It is a constraint because, internally, it enforces that start + size == end.
470
471    It is also a variable as it can appear in specific scheduling constraints:
472    NoOverlap, NoOverlap2D, Cumulative.
473
474    Optionally, an enforcement literal can be added to this constraint, in which
475    case these scheduling constraints will ignore interval variables with
476    enforcement literals assigned to false. Conversely, these constraints will
477    also set these enforcement literals to false if they cannot fit these
478    intervals into the schedule.
479
480    Raises:
481      ValueError: if start, size, end are not defined, or have the wrong type.
482    """
483
484    def __init__(
485        self,
486        model: cp_model_pb2.CpModelProto,
487        var_list: VariableList,
488        start: Union[cp_model_pb2.LinearExpressionProto, int],
489        size: Optional[cp_model_pb2.LinearExpressionProto],
490        end: Optional[cp_model_pb2.LinearExpressionProto],
491        is_present_index: Optional[int],
492        name: Optional[str],
493    ) -> None:
494        self.__model: cp_model_pb2.CpModelProto = model
495        self.__var_list: VariableList = var_list
496        self.__index: int
497        self.__ct: cp_model_pb2.ConstraintProto
498        # As with the IntVar::__init__ method, we hack the __init__ method to
499        # support two use cases:
500        #   case 1: called when creating a new interval variable.
501        #      {start|size|end} are linear expressions, is_present_index is either
502        #      None or the index of a Boolean literal. name is a string
503        #   case 2: called when querying an existing interval variable.
504        #      start_index is an int, all parameters after are None.
505        if isinstance(start, int):
506            if size is not None:
507                raise ValueError("size should be None")
508            if end is not None:
509                raise ValueError("end should be None")
510            if is_present_index is not None:
511                raise ValueError("is_present_index should be None")
512            self.__index = cast(int, start)
513            self.__ct = model.constraints[self.__index]
514        else:
515            self.__index = len(model.constraints)
516            self.__ct = self.__model.constraints.add()
517            if start is None:
518                raise TypeError("start is not defined")
519            self.__ct.interval.start.CopyFrom(start)
520            if size is None:
521                raise TypeError("size is not defined")
522            self.__ct.interval.size.CopyFrom(size)
523            if end is None:
524                raise TypeError("end is not defined")
525            self.__ct.interval.end.CopyFrom(end)
526            if is_present_index is not None:
527                self.__ct.enforcement_literal.append(is_present_index)
528            if name:
529                self.__ct.name = name
530
531    @property
532    def index(self) -> int:
533        """Returns the index of the interval constraint in the model."""
534        return self.__index
535
536    @property
537    def proto(self) -> cp_model_pb2.IntervalConstraintProto:
538        """Returns the interval protobuf."""
539        return self.__ct.interval
540
541    def __str__(self):
542        return self.__ct.name
543
544    def __repr__(self):
545        interval = self.__ct.interval
546        if self.__ct.enforcement_literal:
547            return (
548                f"{self.__ct.name}(start ="
549                f" {short_expr_name(self.__model, interval.start)}, size ="
550                f" {short_expr_name(self.__model, interval.size)}, end ="
551                f" {short_expr_name(self.__model, interval.end)}, is_present ="
552                f" {short_name(self.__model, self.__ct.enforcement_literal[0])})"
553            )
554        else:
555            return (
556                f"{self.__ct.name}(start ="
557                f" {short_expr_name(self.__model, interval.start)}, size ="
558                f" {short_expr_name(self.__model, interval.size)}, end ="
559                f" {short_expr_name(self.__model, interval.end)})"
560            )
561
562    @property
563    def name(self) -> str:
564        if not self.__ct or not self.__ct.name:
565            return ""
566        return self.__ct.name
567
568    def start_expr(self) -> LinearExprT:
569        return self.__var_list.rebuild_expr(self.__ct.interval.start)
570
571    def size_expr(self) -> LinearExprT:
572        return self.__var_list.rebuild_expr(self.__ct.interval.size)
573
574    def end_expr(self) -> LinearExprT:
575        return self.__var_list.rebuild_expr(self.__ct.interval.end)
576
577    # Pre PEP8 compatibility.
578    # pylint: disable=invalid-name
579    def Name(self) -> str:
580        return self.name
581
582    def Index(self) -> int:
583        return self.index
584
585    def Proto(self) -> cp_model_pb2.IntervalConstraintProto:
586        return self.proto
587
588    StartExpr = start_expr
589    SizeExpr = size_expr
590    EndExpr = end_expr
591
592    # pylint: enable=invalid-name

Represents an Interval variable.

An interval variable is both a constraint and a variable. It is defined by three integer variables: start, size, and end.

It is a constraint because, internally, it enforces that start + size == end.

It is also a variable as it can appear in specific scheduling constraints: NoOverlap, NoOverlap2D, Cumulative.

Optionally, an enforcement literal can be added to this constraint, in which case these scheduling constraints will ignore interval variables with enforcement literals assigned to false. Conversely, these constraints will also set these enforcement literals to false if they cannot fit these intervals into the schedule.

Raises:
  • ValueError: if start, size, end are not defined, or have the wrong type.
IntervalVar( model: ortools.sat.cp_model_pb2.CpModelProto, var_list: VariableList, start: Union[ortools.sat.cp_model_pb2.LinearExpressionProto, int], size: Optional[ortools.sat.cp_model_pb2.LinearExpressionProto], end: Optional[ortools.sat.cp_model_pb2.LinearExpressionProto], is_present_index: Optional[int], name: Optional[str])
484    def __init__(
485        self,
486        model: cp_model_pb2.CpModelProto,
487        var_list: VariableList,
488        start: Union[cp_model_pb2.LinearExpressionProto, int],
489        size: Optional[cp_model_pb2.LinearExpressionProto],
490        end: Optional[cp_model_pb2.LinearExpressionProto],
491        is_present_index: Optional[int],
492        name: Optional[str],
493    ) -> None:
494        self.__model: cp_model_pb2.CpModelProto = model
495        self.__var_list: VariableList = var_list
496        self.__index: int
497        self.__ct: cp_model_pb2.ConstraintProto
498        # As with the IntVar::__init__ method, we hack the __init__ method to
499        # support two use cases:
500        #   case 1: called when creating a new interval variable.
501        #      {start|size|end} are linear expressions, is_present_index is either
502        #      None or the index of a Boolean literal. name is a string
503        #   case 2: called when querying an existing interval variable.
504        #      start_index is an int, all parameters after are None.
505        if isinstance(start, int):
506            if size is not None:
507                raise ValueError("size should be None")
508            if end is not None:
509                raise ValueError("end should be None")
510            if is_present_index is not None:
511                raise ValueError("is_present_index should be None")
512            self.__index = cast(int, start)
513            self.__ct = model.constraints[self.__index]
514        else:
515            self.__index = len(model.constraints)
516            self.__ct = self.__model.constraints.add()
517            if start is None:
518                raise TypeError("start is not defined")
519            self.__ct.interval.start.CopyFrom(start)
520            if size is None:
521                raise TypeError("size is not defined")
522            self.__ct.interval.size.CopyFrom(size)
523            if end is None:
524                raise TypeError("end is not defined")
525            self.__ct.interval.end.CopyFrom(end)
526            if is_present_index is not None:
527                self.__ct.enforcement_literal.append(is_present_index)
528            if name:
529                self.__ct.name = name
index: int
531    @property
532    def index(self) -> int:
533        """Returns the index of the interval constraint in the model."""
534        return self.__index

Returns the index of the interval constraint in the model.

536    @property
537    def proto(self) -> cp_model_pb2.IntervalConstraintProto:
538        """Returns the interval protobuf."""
539        return self.__ct.interval

Returns the interval protobuf.

name: str
562    @property
563    def name(self) -> str:
564        if not self.__ct or not self.__ct.name:
565            return ""
566        return self.__ct.name
def start_expr( self) -> Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]:
568    def start_expr(self) -> LinearExprT:
569        return self.__var_list.rebuild_expr(self.__ct.interval.start)
def size_expr( self) -> Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]:
571    def size_expr(self) -> LinearExprT:
572        return self.__var_list.rebuild_expr(self.__ct.interval.size)
def end_expr( self) -> Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]:
574    def end_expr(self) -> LinearExprT:
575        return self.__var_list.rebuild_expr(self.__ct.interval.end)
def Name(self) -> str:
579    def Name(self) -> str:
580        return self.name
def Index(self) -> int:
582    def Index(self) -> int:
583        return self.index
def Proto(self) -> ortools.sat.cp_model_pb2.IntervalConstraintProto:
585    def Proto(self) -> cp_model_pb2.IntervalConstraintProto:
586        return self.proto
def StartExpr( self) -> Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]:
568    def start_expr(self) -> LinearExprT:
569        return self.__var_list.rebuild_expr(self.__ct.interval.start)
def SizeExpr( self) -> Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]:
571    def size_expr(self) -> LinearExprT:
572        return self.__var_list.rebuild_expr(self.__ct.interval.size)
def EndExpr( self) -> Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]:
574    def end_expr(self) -> LinearExprT:
575        return self.__var_list.rebuild_expr(self.__ct.interval.end)
def object_is_a_true_literal( literal: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> bool:
595def object_is_a_true_literal(literal: LiteralT) -> bool:
596    """Checks if literal is either True, or a Boolean literals fixed to True."""
597    if isinstance(literal, IntVar):
598        proto = literal.proto
599        return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1
600    if isinstance(literal, cmh.NotBooleanVariable):
601        proto = literal.negated().proto
602        return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0
603    if isinstance(literal, IntegralTypes):
604        return int(literal) == 1
605    return False

Checks if literal is either True, or a Boolean literals fixed to True.

def object_is_a_false_literal( literal: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> bool:
608def object_is_a_false_literal(literal: LiteralT) -> bool:
609    """Checks if literal is either False, or a Boolean literals fixed to False."""
610    if isinstance(literal, IntVar):
611        proto = literal.proto
612        return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0
613    if isinstance(literal, cmh.NotBooleanVariable):
614        proto = literal.negated().proto
615        return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1
616    if isinstance(literal, IntegralTypes):
617        return int(literal) == 0
618    return False

Checks if literal is either False, or a Boolean literals fixed to False.

class CpModel:
 621class CpModel:
 622    """Methods for building a CP model.
 623
 624    Methods beginning with:
 625
 626    * ```New``` create integer, boolean, or interval variables.
 627    * ```add``` create new constraints and add them to the model.
 628    """
 629
 630    def __init__(self) -> None:
 631        self.__model: cp_model_pb2.CpModelProto = cp_model_pb2.CpModelProto()
 632        self.__constant_map: Dict[IntegralT, int] = {}
 633        self.__var_list: VariableList = VariableList()
 634
 635    # Naming.
 636    @property
 637    def name(self) -> str:
 638        """Returns the name of the model."""
 639        if not self.__model or not self.__model.name:
 640            return ""
 641        return self.__model.name
 642
 643    @name.setter
 644    def name(self, name: str):
 645        """Sets the name of the model."""
 646        self.__model.name = name
 647
 648    # Integer variable.
 649
 650    def _append_int_var(self, var: IntVar) -> IntVar:
 651        """Appends an integer variable to the list of variables."""
 652        self.__var_list.append(var)
 653        return var
 654
 655    def _get_int_var(self, index: int) -> IntVar:
 656        return self.__var_list.get(index)
 657
 658    def rebuild_from_linear_expression_proto(
 659        self,
 660        proto: cp_model_pb2.LinearExpressionProto,
 661    ) -> LinearExpr:
 662        return self.__var_list.rebuild_expr(proto)
 663
 664    def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar:
 665        """Create an integer variable with domain [lb, ub].
 666
 667        The CP-SAT solver is limited to integer variables. If you have fractional
 668        values, scale them up so that they become integers; if you have strings,
 669        encode them as integers.
 670
 671        Args:
 672          lb: Lower bound for the variable.
 673          ub: Upper bound for the variable.
 674          name: The name of the variable.
 675
 676        Returns:
 677          a variable whose domain is [lb, ub].
 678        """
 679        domain_is_boolean = lb >= 0 and ub <= 1
 680        return self._append_int_var(
 681            IntVar(
 682                self.__model,
 683                sorted_interval_list.Domain(lb, ub),
 684                domain_is_boolean,
 685                name,
 686            )
 687        )
 688
 689    def new_int_var_from_domain(
 690        self, domain: sorted_interval_list.Domain, name: str
 691    ) -> IntVar:
 692        """Create an integer variable from a domain.
 693
 694        A domain is a set of integers specified by a collection of intervals.
 695        For example, `model.new_int_var_from_domain(cp_model.
 696             Domain.from_intervals([[1, 2], [4, 6]]), 'x')`
 697
 698        Args:
 699          domain: An instance of the Domain class.
 700          name: The name of the variable.
 701
 702        Returns:
 703            a variable whose domain is the given domain.
 704        """
 705        domain_is_boolean = domain.min() >= 0 and domain.max() <= 1
 706        return self._append_int_var(
 707            IntVar(self.__model, domain, domain_is_boolean, name)
 708        )
 709
 710    def new_bool_var(self, name: str) -> IntVar:
 711        """Creates a 0-1 variable with the given name."""
 712        return self._append_int_var(
 713            IntVar(self.__model, sorted_interval_list.Domain(0, 1), True, name)
 714        )
 715
 716    def new_constant(self, value: IntegralT) -> IntVar:
 717        """Declares a constant integer."""
 718        index: int = self.get_or_make_index_from_constant(value)
 719        return self._get_int_var(index)
 720
 721    def new_int_var_series(
 722        self,
 723        name: str,
 724        index: pd.Index,
 725        lower_bounds: Union[IntegralT, pd.Series],
 726        upper_bounds: Union[IntegralT, pd.Series],
 727    ) -> pd.Series:
 728        """Creates a series of (scalar-valued) variables with the given name.
 729
 730        Args:
 731          name (str): Required. The name of the variable set.
 732          index (pd.Index): Required. The index to use for the variable set.
 733          lower_bounds (Union[int, pd.Series]): A lower bound for variables in the
 734            set. If a `pd.Series` is passed in, it will be based on the
 735            corresponding values of the pd.Series.
 736          upper_bounds (Union[int, pd.Series]): An upper bound for variables in the
 737            set. If a `pd.Series` is passed in, it will be based on the
 738            corresponding values of the pd.Series.
 739
 740        Returns:
 741          pd.Series: The variable set indexed by its corresponding dimensions.
 742
 743        Raises:
 744          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 745          ValueError: if the `name` is not a valid identifier or already exists.
 746          ValueError: if the `lowerbound` is greater than the `upperbound`.
 747          ValueError: if the index of `lower_bound`, or `upper_bound` does not match
 748          the input index.
 749        """
 750        if not isinstance(index, pd.Index):
 751            raise TypeError("Non-index object is used as index")
 752        if not name.isidentifier():
 753            raise ValueError(f"name={name!r} is not a valid identifier")
 754        if (
 755            isinstance(lower_bounds, IntegralTypes)
 756            and isinstance(upper_bounds, IntegralTypes)
 757            and lower_bounds > upper_bounds
 758        ):
 759            raise ValueError(
 760                f"lower_bound={lower_bounds} is greater than"
 761                f" upper_bound={upper_bounds} for variable set={name}"
 762            )
 763
 764        lower_bounds = _convert_to_integral_series_and_validate_index(
 765            lower_bounds, index
 766        )
 767        upper_bounds = _convert_to_integral_series_and_validate_index(
 768            upper_bounds, index
 769        )
 770        return pd.Series(
 771            index=index,
 772            data=[
 773                # pylint: disable=g-complex-comprehension
 774                self._append_int_var(
 775                    IntVar(
 776                        model=self.__model,
 777                        name=f"{name}[{i}]",
 778                        domain=sorted_interval_list.Domain(
 779                            lower_bounds[i], upper_bounds[i]
 780                        ),
 781                        is_boolean=lower_bounds[i] >= 0 and upper_bounds[i] <= 1,
 782                    )
 783                )
 784                for i in index
 785            ],
 786        )
 787
 788    def new_bool_var_series(
 789        self,
 790        name: str,
 791        index: pd.Index,
 792    ) -> pd.Series:
 793        """Creates a series of (scalar-valued) variables with the given name.
 794
 795        Args:
 796          name (str): Required. The name of the variable set.
 797          index (pd.Index): Required. The index to use for the variable set.
 798
 799        Returns:
 800          pd.Series: The variable set indexed by its corresponding dimensions.
 801
 802        Raises:
 803          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 804          ValueError: if the `name` is not a valid identifier or already exists.
 805        """
 806        if not isinstance(index, pd.Index):
 807            raise TypeError("Non-index object is used as index")
 808        if not name.isidentifier():
 809            raise ValueError(f"name={name!r} is not a valid identifier")
 810        return pd.Series(
 811            index=index,
 812            data=[
 813                # pylint: disable=g-complex-comprehension
 814                self._append_int_var(
 815                    IntVar(
 816                        model=self.__model,
 817                        name=f"{name}[{i}]",
 818                        domain=sorted_interval_list.Domain(0, 1),
 819                        is_boolean=True,
 820                    )
 821                )
 822                for i in index
 823            ],
 824        )
 825
 826    # Linear constraints.
 827
 828    def add_linear_constraint(
 829        self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT
 830    ) -> Constraint:
 831        """Adds the constraint: `lb <= linear_expr <= ub`."""
 832        return self.add_linear_expression_in_domain(
 833            linear_expr, sorted_interval_list.Domain(lb, ub)
 834        )
 835
 836    def add_linear_expression_in_domain(
 837        self,
 838        linear_expr: LinearExprT,
 839        domain: sorted_interval_list.Domain,
 840    ) -> Constraint:
 841        """Adds the constraint: `linear_expr` in `domain`."""
 842        if isinstance(linear_expr, LinearExpr):
 843            ble = BoundedLinearExpression(linear_expr, domain)
 844            if not ble.ok:
 845                raise TypeError(
 846                    "Cannot add a linear expression containing floating point"
 847                    f" coefficients or constants: {type(linear_expr).__name__!r}"
 848                )
 849            return self.add(ble)
 850        if isinstance(linear_expr, IntegralTypes):
 851            if not domain.contains(int(linear_expr)):
 852                return self.add_bool_or([])  # Evaluate to false.
 853            else:
 854                return self.add_bool_and([])  # Evaluate to true.
 855        raise TypeError(
 856            "not supported:"
 857            f" CpModel.add_linear_expression_in_domain({type(linear_expr).__name__!r})"
 858        )
 859
 860    def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint:
 861        """Adds a `BoundedLinearExpression` to the model.
 862
 863        Args:
 864          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
 865
 866        Returns:
 867          An instance of the `Constraint` class.
 868
 869        Raises:
 870          TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean.
 871        """
 872        if isinstance(ct, BoundedLinearExpression):
 873            result = Constraint(self)
 874            model_ct = self.__model.constraints[result.index]
 875            for var in ct.vars:
 876                model_ct.linear.vars.append(var.index)
 877            model_ct.linear.coeffs.extend(ct.coeffs)
 878            model_ct.linear.domain.extend(
 879                [
 880                    cmn.capped_subtraction(x, ct.offset)
 881                    for x in ct.bounds.flattened_intervals()
 882                ]
 883            )
 884            return result
 885        if ct and cmn.is_boolean(ct):
 886            return self.add_bool_or([True])
 887        if not ct and cmn.is_boolean(ct):
 888            return self.add_bool_or([])  # Evaluate to false.
 889        raise TypeError(f"not supported: CpModel.add({type(ct).__name__!r})")
 890
 891    # General Integer Constraints.
 892
 893    @overload
 894    def add_all_different(self, expressions: Iterable[LinearExprT]) -> Constraint: ...
 895
 896    @overload
 897    def add_all_different(self, *expressions: LinearExprT) -> Constraint: ...
 898
 899    def add_all_different(self, *expressions):
 900        """Adds AllDifferent(expressions).
 901
 902        This constraint forces all expressions to have different values.
 903
 904        Args:
 905          *expressions: simple expressions of the form a * var + constant.
 906
 907        Returns:
 908          An instance of the `Constraint` class.
 909        """
 910        ct = Constraint(self)
 911        model_ct = self.__model.constraints[ct.index]
 912        expanded = expand_generator_or_tuple(expressions)
 913        model_ct.all_diff.exprs.extend(
 914            self.parse_linear_expression(x) for x in expanded
 915        )
 916        return ct
 917
 918    def add_element(
 919        self,
 920        index: LinearExprT,
 921        expressions: Sequence[LinearExprT],
 922        target: LinearExprT,
 923    ) -> Constraint:
 924        """Adds the element constraint: `expressions[index] == target`.
 925
 926        Args:
 927          index: The index of the selected expression in the array. It must be an
 928            affine expression (a * var + b).
 929          expressions: A list of affine expressions.
 930          target: The expression constrained to be equal to the selected expression.
 931            It must be an affine expression (a * var + b).
 932
 933        Returns:
 934          An instance of the `Constraint` class.
 935        """
 936
 937        if not expressions:
 938            raise ValueError("add_element expects a non-empty expressions array")
 939
 940        if isinstance(index, IntegralTypes):
 941            expression: LinearExprT = list(expressions)[int(index)]
 942            return self.add(expression == target)
 943
 944        ct = Constraint(self)
 945        model_ct = self.__model.constraints[ct.index]
 946        model_ct.element.linear_index.CopyFrom(self.parse_linear_expression(index))
 947        model_ct.element.exprs.extend(
 948            [self.parse_linear_expression(e) for e in expressions]
 949        )
 950        model_ct.element.linear_target.CopyFrom(self.parse_linear_expression(target))
 951        return ct
 952
 953    def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
 954        """Adds Circuit(arcs).
 955
 956        Adds a circuit constraint from a sparse list of arcs that encode the graph.
 957
 958        A circuit is a unique Hamiltonian cycle in a subgraph of the total
 959        graph. In case a node 'i' is not in the cycle, then there must be a
 960        loop arc 'i -> i' associated with a true literal. Otherwise
 961        this constraint will fail.
 962
 963        Args:
 964          arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
 965            literal). The arc is selected in the circuit if the literal is true.
 966            Both source_node and destination_node must be integers between 0 and the
 967            number of nodes - 1.
 968
 969        Returns:
 970          An instance of the `Constraint` class.
 971
 972        Raises:
 973          ValueError: If the list of arcs is empty.
 974        """
 975        if not arcs:
 976            raise ValueError("add_circuit expects a non-empty array of arcs")
 977        ct = Constraint(self)
 978        model_ct = self.__model.constraints[ct.index]
 979        for arc in arcs:
 980            model_ct.circuit.tails.append(arc[0])
 981            model_ct.circuit.heads.append(arc[1])
 982            model_ct.circuit.literals.append(self.get_or_make_boolean_index(arc[2]))
 983        return ct
 984
 985    def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
 986        """Adds a multiple circuit constraint, aka the 'VRP' constraint.
 987
 988        The direct graph where arc #i (from tails[i] to head[i]) is present iff
 989        literals[i] is true must satisfy this set of properties:
 990        - #incoming arcs == 1 except for node 0.
 991        - #outgoing arcs == 1 except for node 0.
 992        - for node zero, #incoming arcs == #outgoing arcs.
 993        - There are no duplicate arcs.
 994        - Self-arcs are allowed except for node 0.
 995        - There is no cycle in this graph, except through node 0.
 996
 997        Args:
 998          arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
 999            literal). The arc is selected in the circuit if the literal is true.
1000            Both source_node and destination_node must be integers between 0 and the
1001            number of nodes - 1.
1002
1003        Returns:
1004          An instance of the `Constraint` class.
1005
1006        Raises:
1007          ValueError: If the list of arcs is empty.
1008        """
1009        if not arcs:
1010            raise ValueError("add_multiple_circuit expects a non-empty array of arcs")
1011        ct = Constraint(self)
1012        model_ct = self.__model.constraints[ct.index]
1013        for arc in arcs:
1014            model_ct.routes.tails.append(arc[0])
1015            model_ct.routes.heads.append(arc[1])
1016            model_ct.routes.literals.append(self.get_or_make_boolean_index(arc[2]))
1017        return ct
1018
1019    def add_allowed_assignments(
1020        self,
1021        expressions: Sequence[LinearExprT],
1022        tuples_list: Iterable[Sequence[IntegralT]],
1023    ) -> Constraint:
1024        """Adds AllowedAssignments(expressions, tuples_list).
1025
1026        An AllowedAssignments constraint is a constraint on an array of affine
1027        expressions, which requires that when all expressions are assigned values,
1028        the
1029        resulting array equals one of the  tuples in `tuple_list`.
1030
1031        Args:
1032          expressions: A list of affine expressions (a * var + b).
1033          tuples_list: A list of admissible tuples. Each tuple must have the same
1034            length as the expressions, and the ith value of a tuple corresponds to
1035            the ith expression.
1036
1037        Returns:
1038          An instance of the `Constraint` class.
1039
1040        Raises:
1041          TypeError: If a tuple does not have the same size as the list of
1042              expressions.
1043          ValueError: If the array of expressions is empty.
1044        """
1045
1046        if not expressions:
1047            raise ValueError(
1048                "add_allowed_assignments expects a non-empty expressions array"
1049            )
1050
1051        ct: Constraint = Constraint(self)
1052        model_ct = self.__model.constraints[ct.index]
1053        model_ct.table.exprs.extend(
1054            [self.parse_linear_expression(e) for e in expressions]
1055        )
1056        arity: int = len(expressions)
1057        for one_tuple in tuples_list:
1058            if len(one_tuple) != arity:
1059                raise TypeError(f"Tuple {one_tuple!r} has the wrong arity")
1060
1061        # duck-typing (no explicit type checks here)
1062        try:
1063            for one_tuple in tuples_list:
1064                model_ct.table.values.extend(one_tuple)
1065        except ValueError as ex:
1066            raise TypeError(
1067                "add_xxx_assignment: Not an integer or does not fit in an int64_t:"
1068                f" {type(ex.args).__name__!r}"
1069            ) from ex
1070
1071        return ct
1072
1073    def add_forbidden_assignments(
1074        self,
1075        expressions: Sequence[LinearExprT],
1076        tuples_list: Iterable[Sequence[IntegralT]],
1077    ) -> Constraint:
1078        """Adds add_forbidden_assignments(expressions, [tuples_list]).
1079
1080        A ForbiddenAssignments constraint is a constraint on an array of affine
1081        expressions where the list of impossible combinations is provided in the
1082        tuples list.
1083
1084        Args:
1085          expressions: A list of affine expressions (a * var + b).
1086          tuples_list: A list of forbidden tuples. Each tuple must have the same
1087            length as the expressions, and the *i*th value of a tuple corresponds to
1088            the *i*th expression.
1089
1090        Returns:
1091          An instance of the `Constraint` class.
1092
1093        Raises:
1094          TypeError: If a tuple does not have the same size as the list of
1095                     expressions.
1096          ValueError: If the array of expressions is empty.
1097        """
1098
1099        if not expressions:
1100            raise ValueError(
1101                "add_forbidden_assignments expects a non-empty expressions array"
1102            )
1103
1104        index: int = len(self.__model.constraints)
1105        ct: Constraint = self.add_allowed_assignments(expressions, tuples_list)
1106        self.__model.constraints[index].table.negated = True
1107        return ct
1108
1109    def add_automaton(
1110        self,
1111        transition_expressions: Sequence[LinearExprT],
1112        starting_state: IntegralT,
1113        final_states: Sequence[IntegralT],
1114        transition_triples: Sequence[Tuple[IntegralT, IntegralT, IntegralT]],
1115    ) -> Constraint:
1116        """Adds an automaton constraint.
1117
1118        An automaton constraint takes a list of affine expressions (a * var + b) (of
1119        size *n*), an initial state, a set of final states, and a set of
1120        transitions. A transition is a triplet (*tail*, *transition*, *head*), where
1121        *tail* and *head* are states, and *transition* is the label of an arc from
1122        *head* to *tail*, corresponding to the value of one expression in the list
1123        of
1124        expressions.
1125
1126        This automaton will be unrolled into a flow with *n* + 1 phases. Each phase
1127        contains the possible states of the automaton. The first state contains the
1128        initial state. The last phase contains the final states.
1129
1130        Between two consecutive phases *i* and *i* + 1, the automaton creates a set
1131        of arcs. For each transition (*tail*, *transition*, *head*), it will add
1132        an arc from the state *tail* of phase *i* and the state *head* of phase
1133        *i* + 1. This arc is labeled by the value *transition* of the expression
1134        `expressions[i]`. That is, this arc can only be selected if `expressions[i]`
1135        is assigned the value *transition*.
1136
1137        A feasible solution of this constraint is an assignment of expressions such
1138        that, starting from the initial state in phase 0, there is a path labeled by
1139        the values of the expressions that ends in one of the final states in the
1140        final phase.
1141
1142        Args:
1143          transition_expressions: A non-empty list of affine expressions (a * var +
1144            b) whose values correspond to the labels of the arcs traversed by the
1145            automaton.
1146          starting_state: The initial state of the automaton.
1147          final_states: A non-empty list of admissible final states.
1148          transition_triples: A list of transitions for the automaton, in the
1149            following format (current_state, variable_value, next_state).
1150
1151        Returns:
1152          An instance of the `Constraint` class.
1153
1154        Raises:
1155          ValueError: if `transition_expressions`, `final_states`, or
1156            `transition_triples` are empty.
1157        """
1158
1159        if not transition_expressions:
1160            raise ValueError(
1161                "add_automaton expects a non-empty transition_expressions array"
1162            )
1163        if not final_states:
1164            raise ValueError("add_automaton expects some final states")
1165
1166        if not transition_triples:
1167            raise ValueError("add_automaton expects some transition triples")
1168
1169        ct = Constraint(self)
1170        model_ct = self.__model.constraints[ct.index]
1171        model_ct.automaton.exprs.extend(
1172            [self.parse_linear_expression(e) for e in transition_expressions]
1173        )
1174        model_ct.automaton.starting_state = starting_state
1175        for v in final_states:
1176            model_ct.automaton.final_states.append(v)
1177        for t in transition_triples:
1178            if len(t) != 3:
1179                raise TypeError(f"Tuple {t!r} has the wrong arity (!= 3)")
1180            model_ct.automaton.transition_tail.append(t[0])
1181            model_ct.automaton.transition_label.append(t[1])
1182            model_ct.automaton.transition_head.append(t[2])
1183        return ct
1184
1185    def add_inverse(
1186        self,
1187        variables: Sequence[VariableT],
1188        inverse_variables: Sequence[VariableT],
1189    ) -> Constraint:
1190        """Adds Inverse(variables, inverse_variables).
1191
1192        An inverse constraint enforces that if `variables[i]` is assigned a value
1193        `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa.
1194
1195        Args:
1196          variables: An array of integer variables.
1197          inverse_variables: An array of integer variables.
1198
1199        Returns:
1200          An instance of the `Constraint` class.
1201
1202        Raises:
1203          TypeError: if variables and inverse_variables have different lengths, or
1204              if they are empty.
1205        """
1206
1207        if not variables or not inverse_variables:
1208            raise TypeError("The Inverse constraint does not accept empty arrays")
1209        if len(variables) != len(inverse_variables):
1210            raise TypeError(
1211                "In the inverse constraint, the two array variables and"
1212                " inverse_variables must have the same length."
1213            )
1214        ct = Constraint(self)
1215        model_ct = self.__model.constraints[ct.index]
1216        model_ct.inverse.f_direct.extend([self.get_or_make_index(x) for x in variables])
1217        model_ct.inverse.f_inverse.extend(
1218            [self.get_or_make_index(x) for x in inverse_variables]
1219        )
1220        return ct
1221
1222    def add_reservoir_constraint(
1223        self,
1224        times: Iterable[LinearExprT],
1225        level_changes: Iterable[LinearExprT],
1226        min_level: int,
1227        max_level: int,
1228    ) -> Constraint:
1229        """Adds Reservoir(times, level_changes, min_level, max_level).
1230
1231        Maintains a reservoir level within bounds. The water level starts at 0, and
1232        at any time, it must be between min_level and max_level.
1233
1234        If the affine expression `times[i]` is assigned a value t, then the current
1235        level changes by `level_changes[i]`, which is constant, at time t.
1236
1237         Note that min level must be <= 0, and the max level must be >= 0. Please
1238         use fixed level_changes to simulate initial state.
1239
1240         Therefore, at any time:
1241             sum(level_changes[i] if times[i] <= t) in [min_level, max_level]
1242
1243        Args:
1244          times: A list of 1-var affine expressions (a * x + b) which specify the
1245            time of the filling or emptying the reservoir.
1246          level_changes: A list of integer values that specifies the amount of the
1247            emptying or filling. Currently, variable demands are not supported.
1248          min_level: At any time, the level of the reservoir must be greater or
1249            equal than the min level.
1250          max_level: At any time, the level of the reservoir must be less or equal
1251            than the max level.
1252
1253        Returns:
1254          An instance of the `Constraint` class.
1255
1256        Raises:
1257          ValueError: if max_level < min_level.
1258
1259          ValueError: if max_level < 0.
1260
1261          ValueError: if min_level > 0
1262        """
1263
1264        if max_level < min_level:
1265            raise ValueError("Reservoir constraint must have a max_level >= min_level")
1266
1267        if max_level < 0:
1268            raise ValueError("Reservoir constraint must have a max_level >= 0")
1269
1270        if min_level > 0:
1271            raise ValueError("Reservoir constraint must have a min_level <= 0")
1272
1273        ct = Constraint(self)
1274        model_ct = self.__model.constraints[ct.index]
1275        model_ct.reservoir.time_exprs.extend(
1276            [self.parse_linear_expression(x) for x in times]
1277        )
1278        model_ct.reservoir.level_changes.extend(
1279            [self.parse_linear_expression(x) for x in level_changes]
1280        )
1281        model_ct.reservoir.min_level = min_level
1282        model_ct.reservoir.max_level = max_level
1283        return ct
1284
1285    def add_reservoir_constraint_with_active(
1286        self,
1287        times: Iterable[LinearExprT],
1288        level_changes: Iterable[LinearExprT],
1289        actives: Iterable[LiteralT],
1290        min_level: int,
1291        max_level: int,
1292    ) -> Constraint:
1293        """Adds Reservoir(times, level_changes, actives, min_level, max_level).
1294
1295        Maintains a reservoir level within bounds. The water level starts at 0, and
1296        at any time, it must be between min_level and max_level.
1297
1298        If the variable `times[i]` is assigned a value t, and `actives[i]` is
1299        `True`, then the current level changes by `level_changes[i]`, which is
1300        constant,
1301        at time t.
1302
1303         Note that min level must be <= 0, and the max level must be >= 0. Please
1304         use fixed level_changes to simulate initial state.
1305
1306         Therefore, at any time:
1307             sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level,
1308             max_level]
1309
1310
1311        The array of boolean variables 'actives', if defined, indicates which
1312        actions are actually performed.
1313
1314        Args:
1315          times: A list of 1-var affine expressions (a * x + b) which specify the
1316            time of the filling or emptying the reservoir.
1317          level_changes: A list of integer values that specifies the amount of the
1318            emptying or filling. Currently, variable demands are not supported.
1319          actives: a list of boolean variables. They indicates if the
1320            emptying/refilling events actually take place.
1321          min_level: At any time, the level of the reservoir must be greater or
1322            equal than the min level.
1323          max_level: At any time, the level of the reservoir must be less or equal
1324            than the max level.
1325
1326        Returns:
1327          An instance of the `Constraint` class.
1328
1329        Raises:
1330          ValueError: if max_level < min_level.
1331
1332          ValueError: if max_level < 0.
1333
1334          ValueError: if min_level > 0
1335        """
1336
1337        if max_level < min_level:
1338            raise ValueError("Reservoir constraint must have a max_level >= min_level")
1339
1340        if max_level < 0:
1341            raise ValueError("Reservoir constraint must have a max_level >= 0")
1342
1343        if min_level > 0:
1344            raise ValueError("Reservoir constraint must have a min_level <= 0")
1345
1346        ct = Constraint(self)
1347        model_ct = self.__model.constraints[ct.index]
1348        model_ct.reservoir.time_exprs.extend(
1349            [self.parse_linear_expression(x) for x in times]
1350        )
1351        model_ct.reservoir.level_changes.extend(
1352            [self.parse_linear_expression(x) for x in level_changes]
1353        )
1354        model_ct.reservoir.active_literals.extend(
1355            [self.get_or_make_boolean_index(x) for x in actives]
1356        )
1357        model_ct.reservoir.min_level = min_level
1358        model_ct.reservoir.max_level = max_level
1359        return ct
1360
1361    def add_map_domain(
1362        self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0
1363    ):
1364        """Adds `var == i + offset <=> bool_var_array[i] == true for all i`."""
1365
1366        for i, bool_var in enumerate(bool_var_array):
1367            b_index = bool_var.index
1368            var_index = var.index
1369            model_ct = self.__model.constraints.add()
1370            model_ct.linear.vars.append(var_index)
1371            model_ct.linear.coeffs.append(1)
1372            offset_as_int = int(offset)
1373            model_ct.linear.domain.extend([offset_as_int + i, offset_as_int + i])
1374            model_ct.enforcement_literal.append(b_index)
1375
1376            model_ct = self.__model.constraints.add()
1377            model_ct.linear.vars.append(var_index)
1378            model_ct.linear.coeffs.append(1)
1379            model_ct.enforcement_literal.append(-b_index - 1)
1380            if offset + i - 1 >= INT_MIN:
1381                model_ct.linear.domain.extend([INT_MIN, offset_as_int + i - 1])
1382            if offset + i + 1 <= INT_MAX:
1383                model_ct.linear.domain.extend([offset_as_int + i + 1, INT_MAX])
1384
1385    def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint:
1386        """Adds `a => b` (`a` implies `b`)."""
1387        ct = Constraint(self)
1388        model_ct = self.__model.constraints[ct.index]
1389        model_ct.bool_or.literals.append(self.get_or_make_boolean_index(b))
1390        model_ct.enforcement_literal.append(self.get_or_make_boolean_index(a))
1391        return ct
1392
1393    @overload
1394    def add_bool_or(self, literals: Iterable[LiteralT]) -> Constraint: ...
1395
1396    @overload
1397    def add_bool_or(self, *literals: LiteralT) -> Constraint: ...
1398
1399    def add_bool_or(self, *literals):
1400        """Adds `Or(literals) == true`: sum(literals) >= 1."""
1401        ct = Constraint(self)
1402        model_ct = self.__model.constraints[ct.index]
1403        model_ct.bool_or.literals.extend(
1404            [
1405                self.get_or_make_boolean_index(x)
1406                for x in expand_generator_or_tuple(literals)
1407            ]
1408        )
1409        return ct
1410
1411    @overload
1412    def add_at_least_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
1413
1414    @overload
1415    def add_at_least_one(self, *literals: LiteralT) -> Constraint: ...
1416
1417    def add_at_least_one(self, *literals):
1418        """Same as `add_bool_or`: `sum(literals) >= 1`."""
1419        return self.add_bool_or(*literals)
1420
1421    @overload
1422    def add_at_most_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
1423
1424    @overload
1425    def add_at_most_one(self, *literals: LiteralT) -> Constraint: ...
1426
1427    def add_at_most_one(self, *literals):
1428        """Adds `AtMostOne(literals)`: `sum(literals) <= 1`."""
1429        ct = Constraint(self)
1430        model_ct = self.__model.constraints[ct.index]
1431        model_ct.at_most_one.literals.extend(
1432            [
1433                self.get_or_make_boolean_index(x)
1434                for x in expand_generator_or_tuple(literals)
1435            ]
1436        )
1437        return ct
1438
1439    @overload
1440    def add_exactly_one(self, literals: Iterable[LiteralT]) -> Constraint: ...
1441
1442    @overload
1443    def add_exactly_one(self, *literals: LiteralT) -> Constraint: ...
1444
1445    def add_exactly_one(self, *literals):
1446        """Adds `ExactlyOne(literals)`: `sum(literals) == 1`."""
1447        ct = Constraint(self)
1448        model_ct = self.__model.constraints[ct.index]
1449        model_ct.exactly_one.literals.extend(
1450            [
1451                self.get_or_make_boolean_index(x)
1452                for x in expand_generator_or_tuple(literals)
1453            ]
1454        )
1455        return ct
1456
1457    @overload
1458    def add_bool_and(self, literals: Iterable[LiteralT]) -> Constraint: ...
1459
1460    @overload
1461    def add_bool_and(self, *literals: LiteralT) -> Constraint: ...
1462
1463    def add_bool_and(self, *literals):
1464        """Adds `And(literals) == true`."""
1465        ct = Constraint(self)
1466        model_ct = self.__model.constraints[ct.index]
1467        model_ct.bool_and.literals.extend(
1468            [
1469                self.get_or_make_boolean_index(x)
1470                for x in expand_generator_or_tuple(literals)
1471            ]
1472        )
1473        return ct
1474
1475    @overload
1476    def add_bool_xor(self, literals: Iterable[LiteralT]) -> Constraint: ...
1477
1478    @overload
1479    def add_bool_xor(self, *literals: LiteralT) -> Constraint: ...
1480
1481    def add_bool_xor(self, *literals):
1482        """Adds `XOr(literals) == true`.
1483
1484        In contrast to add_bool_or and add_bool_and, it does not support
1485            .only_enforce_if().
1486
1487        Args:
1488          *literals: the list of literals in the constraint.
1489
1490        Returns:
1491          An `Constraint` object.
1492        """
1493        ct = Constraint(self)
1494        model_ct = self.__model.constraints[ct.index]
1495        model_ct.bool_xor.literals.extend(
1496            [
1497                self.get_or_make_boolean_index(x)
1498                for x in expand_generator_or_tuple(literals)
1499            ]
1500        )
1501        return ct
1502
1503    def add_min_equality(
1504        self, target: LinearExprT, exprs: Iterable[LinearExprT]
1505    ) -> Constraint:
1506        """Adds `target == Min(exprs)`."""
1507        ct = Constraint(self)
1508        model_ct = self.__model.constraints[ct.index]
1509        model_ct.lin_max.exprs.extend(
1510            [self.parse_linear_expression(x, True) for x in exprs]
1511        )
1512        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target, True))
1513        return ct
1514
1515    def add_max_equality(
1516        self, target: LinearExprT, exprs: Iterable[LinearExprT]
1517    ) -> Constraint:
1518        """Adds `target == Max(exprs)`."""
1519        ct = Constraint(self)
1520        model_ct = self.__model.constraints[ct.index]
1521        model_ct.lin_max.exprs.extend([self.parse_linear_expression(x) for x in exprs])
1522        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
1523        return ct
1524
1525    def add_division_equality(
1526        self, target: LinearExprT, num: LinearExprT, denom: LinearExprT
1527    ) -> Constraint:
1528        """Adds `target == num // denom` (integer division rounded towards 0)."""
1529        ct = Constraint(self)
1530        model_ct = self.__model.constraints[ct.index]
1531        model_ct.int_div.exprs.append(self.parse_linear_expression(num))
1532        model_ct.int_div.exprs.append(self.parse_linear_expression(denom))
1533        model_ct.int_div.target.CopyFrom(self.parse_linear_expression(target))
1534        return ct
1535
1536    def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint:
1537        """Adds `target == Abs(expr)`."""
1538        ct = Constraint(self)
1539        model_ct = self.__model.constraints[ct.index]
1540        model_ct.lin_max.exprs.append(self.parse_linear_expression(expr))
1541        model_ct.lin_max.exprs.append(self.parse_linear_expression(expr, True))
1542        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
1543        return ct
1544
1545    def add_modulo_equality(
1546        self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT
1547    ) -> Constraint:
1548        """Adds `target = expr % mod`.
1549
1550        It uses the C convention, that is the result is the remainder of the
1551        integral division rounded towards 0.
1552
1553            For example:
1554            * 10 % 3 = 1
1555            * -10 % 3 = -1
1556            * 10 % -3 = 1
1557            * -10 % -3 = -1
1558
1559        Args:
1560          target: the target expression.
1561          expr: the expression to compute the modulo of.
1562          mod: the modulus expression.
1563
1564        Returns:
1565          A `Constraint` object.
1566        """
1567        ct = Constraint(self)
1568        model_ct = self.__model.constraints[ct.index]
1569        model_ct.int_mod.exprs.append(self.parse_linear_expression(expr))
1570        model_ct.int_mod.exprs.append(self.parse_linear_expression(mod))
1571        model_ct.int_mod.target.CopyFrom(self.parse_linear_expression(target))
1572        return ct
1573
1574    def add_multiplication_equality(
1575        self,
1576        target: LinearExprT,
1577        *expressions: Union[Iterable[LinearExprT], LinearExprT],
1578    ) -> Constraint:
1579        """Adds `target == expressions[0] * .. * expressions[n]`."""
1580        ct = Constraint(self)
1581        model_ct = self.__model.constraints[ct.index]
1582        model_ct.int_prod.exprs.extend(
1583            [
1584                self.parse_linear_expression(expr)
1585                for expr in expand_generator_or_tuple(expressions)
1586            ]
1587        )
1588        model_ct.int_prod.target.CopyFrom(self.parse_linear_expression(target))
1589        return ct
1590
1591    # Scheduling support
1592
1593    def new_interval_var(
1594        self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str
1595    ) -> IntervalVar:
1596        """Creates an interval variable from start, size, and end.
1597
1598        An interval variable is a constraint, that is itself used in other
1599        constraints like NoOverlap.
1600
1601        Internally, it ensures that `start + size == end`.
1602
1603        Args:
1604          start: The start of the interval. It must be of the form a * var + b.
1605          size: The size of the interval. It must be of the form a * var + b.
1606          end: The end of the interval. It must be of the form a * var + b.
1607          name: The name of the interval variable.
1608
1609        Returns:
1610          An `IntervalVar` object.
1611        """
1612
1613        start_expr = self.parse_linear_expression(start)
1614        size_expr = self.parse_linear_expression(size)
1615        end_expr = self.parse_linear_expression(end)
1616        if len(start_expr.vars) > 1:
1617            raise TypeError(
1618                "cp_model.new_interval_var: start must be 1-var affine or constant."
1619            )
1620        if len(size_expr.vars) > 1:
1621            raise TypeError(
1622                "cp_model.new_interval_var: size must be 1-var affine or constant."
1623            )
1624        if len(end_expr.vars) > 1:
1625            raise TypeError(
1626                "cp_model.new_interval_var: end must be 1-var affine or constant."
1627            )
1628        return IntervalVar(
1629            self.__model,
1630            self.__var_list,
1631            start_expr,
1632            size_expr,
1633            end_expr,
1634            None,
1635            name,
1636        )
1637
1638    def new_interval_var_series(
1639        self,
1640        name: str,
1641        index: pd.Index,
1642        starts: Union[LinearExprT, pd.Series],
1643        sizes: Union[LinearExprT, pd.Series],
1644        ends: Union[LinearExprT, pd.Series],
1645    ) -> pd.Series:
1646        """Creates a series of interval variables with the given name.
1647
1648        Args:
1649          name (str): Required. The name of the variable set.
1650          index (pd.Index): Required. The index to use for the variable set.
1651          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1652            set. If a `pd.Series` is passed in, it will be based on the
1653            corresponding values of the pd.Series.
1654          sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1655            set. If a `pd.Series` is passed in, it will be based on the
1656            corresponding values of the pd.Series.
1657          ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1658            set. If a `pd.Series` is passed in, it will be based on the
1659            corresponding values of the pd.Series.
1660
1661        Returns:
1662          pd.Series: The interval variable set indexed by its corresponding
1663          dimensions.
1664
1665        Raises:
1666          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1667          ValueError: if the `name` is not a valid identifier or already exists.
1668          ValueError: if the all the indexes do not match.
1669        """
1670        if not isinstance(index, pd.Index):
1671            raise TypeError("Non-index object is used as index")
1672        if not name.isidentifier():
1673            raise ValueError(f"name={name!r} is not a valid identifier")
1674
1675        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1676        sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
1677        ends = _convert_to_linear_expr_series_and_validate_index(ends, index)
1678        interval_array = []
1679        for i in index:
1680            interval_array.append(
1681                self.new_interval_var(
1682                    start=starts[i],
1683                    size=sizes[i],
1684                    end=ends[i],
1685                    name=f"{name}[{i}]",
1686                )
1687            )
1688        return pd.Series(index=index, data=interval_array)
1689
1690    def new_fixed_size_interval_var(
1691        self, start: LinearExprT, size: IntegralT, name: str
1692    ) -> IntervalVar:
1693        """Creates an interval variable from start, and a fixed size.
1694
1695        An interval variable is a constraint, that is itself used in other
1696        constraints like NoOverlap.
1697
1698        Args:
1699          start: The start of the interval. It must be of the form a * var + b.
1700          size: The size of the interval. It must be an integer value.
1701          name: The name of the interval variable.
1702
1703        Returns:
1704          An `IntervalVar` object.
1705        """
1706        start_expr = self.parse_linear_expression(start)
1707        size_expr = self.parse_linear_expression(size)
1708        end_expr = self.parse_linear_expression(start + size)
1709        if len(start_expr.vars) > 1:
1710            raise TypeError(
1711                "cp_model.new_interval_var: start must be affine or constant."
1712            )
1713        return IntervalVar(
1714            self.__model,
1715            self.__var_list,
1716            start_expr,
1717            size_expr,
1718            end_expr,
1719            None,
1720            name,
1721        )
1722
1723    def new_fixed_size_interval_var_series(
1724        self,
1725        name: str,
1726        index: pd.Index,
1727        starts: Union[LinearExprT, pd.Series],
1728        sizes: Union[IntegralT, pd.Series],
1729    ) -> pd.Series:
1730        """Creates a series of interval variables with the given name.
1731
1732        Args:
1733          name (str): Required. The name of the variable set.
1734          index (pd.Index): Required. The index to use for the variable set.
1735          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1736            set. If a `pd.Series` is passed in, it will be based on the
1737            corresponding values of the pd.Series.
1738          sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1739            the set. If a `pd.Series` is passed in, it will be based on the
1740            corresponding values of the pd.Series.
1741
1742        Returns:
1743          pd.Series: The interval variable set indexed by its corresponding
1744          dimensions.
1745
1746        Raises:
1747          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1748          ValueError: if the `name` is not a valid identifier or already exists.
1749          ValueError: if the all the indexes do not match.
1750        """
1751        if not isinstance(index, pd.Index):
1752            raise TypeError("Non-index object is used as index")
1753        if not name.isidentifier():
1754            raise ValueError(f"name={name!r} is not a valid identifier")
1755
1756        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1757        sizes = _convert_to_integral_series_and_validate_index(sizes, index)
1758        interval_array = []
1759        for i in index:
1760            interval_array.append(
1761                self.new_fixed_size_interval_var(
1762                    start=starts[i],
1763                    size=sizes[i],
1764                    name=f"{name}[{i}]",
1765                )
1766            )
1767        return pd.Series(index=index, data=interval_array)
1768
1769    def new_optional_interval_var(
1770        self,
1771        start: LinearExprT,
1772        size: LinearExprT,
1773        end: LinearExprT,
1774        is_present: LiteralT,
1775        name: str,
1776    ) -> IntervalVar:
1777        """Creates an optional interval var from start, size, end, and is_present.
1778
1779        An optional interval variable is a constraint, that is itself used in other
1780        constraints like NoOverlap. This constraint is protected by a presence
1781        literal that indicates if it is active or not.
1782
1783        Internally, it ensures that `is_present` implies `start + size ==
1784        end`.
1785
1786        Args:
1787          start: The start of the interval. It must be of the form a * var + b.
1788          size: The size of the interval. It must be of the form a * var + b.
1789          end: The end of the interval. It must be of the form a * var + b.
1790          is_present: A literal that indicates if the interval is active or not. A
1791            inactive interval is simply ignored by all constraints.
1792          name: The name of the interval variable.
1793
1794        Returns:
1795          An `IntervalVar` object.
1796        """
1797
1798        # Creates the IntervalConstraintProto object.
1799        is_present_index = self.get_or_make_boolean_index(is_present)
1800        start_expr = self.parse_linear_expression(start)
1801        size_expr = self.parse_linear_expression(size)
1802        end_expr = self.parse_linear_expression(end)
1803        if len(start_expr.vars) > 1:
1804            raise TypeError(
1805                "cp_model.new_interval_var: start must be affine or constant."
1806            )
1807        if len(size_expr.vars) > 1:
1808            raise TypeError(
1809                "cp_model.new_interval_var: size must be affine or constant."
1810            )
1811        if len(end_expr.vars) > 1:
1812            raise TypeError(
1813                "cp_model.new_interval_var: end must be affine or constant."
1814            )
1815        return IntervalVar(
1816            self.__model,
1817            self.__var_list,
1818            start_expr,
1819            size_expr,
1820            end_expr,
1821            is_present_index,
1822            name,
1823        )
1824
1825    def new_optional_interval_var_series(
1826        self,
1827        name: str,
1828        index: pd.Index,
1829        starts: Union[LinearExprT, pd.Series],
1830        sizes: Union[LinearExprT, pd.Series],
1831        ends: Union[LinearExprT, pd.Series],
1832        are_present: Union[LiteralT, pd.Series],
1833    ) -> pd.Series:
1834        """Creates a series of interval variables with the given name.
1835
1836        Args:
1837          name (str): Required. The name of the variable set.
1838          index (pd.Index): Required. The index to use for the variable set.
1839          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1840            set. If a `pd.Series` is passed in, it will be based on the
1841            corresponding values of the pd.Series.
1842          sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1843            set. If a `pd.Series` is passed in, it will be based on the
1844            corresponding values of the pd.Series.
1845          ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1846            set. If a `pd.Series` is passed in, it will be based on the
1847            corresponding values of the pd.Series.
1848          are_present (Union[LiteralT, pd.Series]): The performed literal of each
1849            interval in the set. If a `pd.Series` is passed in, it will be based on
1850            the corresponding values of the pd.Series.
1851
1852        Returns:
1853          pd.Series: The interval variable set indexed by its corresponding
1854          dimensions.
1855
1856        Raises:
1857          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1858          ValueError: if the `name` is not a valid identifier or already exists.
1859          ValueError: if the all the indexes do not match.
1860        """
1861        if not isinstance(index, pd.Index):
1862            raise TypeError("Non-index object is used as index")
1863        if not name.isidentifier():
1864            raise ValueError(f"name={name!r} is not a valid identifier")
1865
1866        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1867        sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
1868        ends = _convert_to_linear_expr_series_and_validate_index(ends, index)
1869        are_present = _convert_to_literal_series_and_validate_index(are_present, index)
1870
1871        interval_array = []
1872        for i in index:
1873            interval_array.append(
1874                self.new_optional_interval_var(
1875                    start=starts[i],
1876                    size=sizes[i],
1877                    end=ends[i],
1878                    is_present=are_present[i],
1879                    name=f"{name}[{i}]",
1880                )
1881            )
1882        return pd.Series(index=index, data=interval_array)
1883
1884    def new_optional_fixed_size_interval_var(
1885        self,
1886        start: LinearExprT,
1887        size: IntegralT,
1888        is_present: LiteralT,
1889        name: str,
1890    ) -> IntervalVar:
1891        """Creates an interval variable from start, and a fixed size.
1892
1893        An interval variable is a constraint, that is itself used in other
1894        constraints like NoOverlap.
1895
1896        Args:
1897          start: The start of the interval. It must be of the form a * var + b.
1898          size: The size of the interval. It must be an integer value.
1899          is_present: A literal that indicates if the interval is active or not. A
1900            inactive interval is simply ignored by all constraints.
1901          name: The name of the interval variable.
1902
1903        Returns:
1904          An `IntervalVar` object.
1905        """
1906        start_expr = self.parse_linear_expression(start)
1907        size_expr = self.parse_linear_expression(size)
1908        end_expr = self.parse_linear_expression(start + size)
1909        if len(start_expr.vars) > 1:
1910            raise TypeError(
1911                "cp_model.new_interval_var: start must be affine or constant."
1912            )
1913        is_present_index = self.get_or_make_boolean_index(is_present)
1914        return IntervalVar(
1915            self.__model,
1916            self.__var_list,
1917            start_expr,
1918            size_expr,
1919            end_expr,
1920            is_present_index,
1921            name,
1922        )
1923
1924    def new_optional_fixed_size_interval_var_series(
1925        self,
1926        name: str,
1927        index: pd.Index,
1928        starts: Union[LinearExprT, pd.Series],
1929        sizes: Union[IntegralT, pd.Series],
1930        are_present: Union[LiteralT, pd.Series],
1931    ) -> pd.Series:
1932        """Creates a series of interval variables with the given name.
1933
1934        Args:
1935          name (str): Required. The name of the variable set.
1936          index (pd.Index): Required. The index to use for the variable set.
1937          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1938            set. If a `pd.Series` is passed in, it will be based on the
1939            corresponding values of the pd.Series.
1940          sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1941            the set. If a `pd.Series` is passed in, it will be based on the
1942            corresponding values of the pd.Series.
1943          are_present (Union[LiteralT, pd.Series]): The performed literal of each
1944            interval in the set. If a `pd.Series` is passed in, it will be based on
1945            the corresponding values of the pd.Series.
1946
1947        Returns:
1948          pd.Series: The interval variable set indexed by its corresponding
1949          dimensions.
1950
1951        Raises:
1952          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1953          ValueError: if the `name` is not a valid identifier or already exists.
1954          ValueError: if the all the indexes do not match.
1955        """
1956        if not isinstance(index, pd.Index):
1957            raise TypeError("Non-index object is used as index")
1958        if not name.isidentifier():
1959            raise ValueError(f"name={name!r} is not a valid identifier")
1960
1961        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1962        sizes = _convert_to_integral_series_and_validate_index(sizes, index)
1963        are_present = _convert_to_literal_series_and_validate_index(are_present, index)
1964        interval_array = []
1965        for i in index:
1966            interval_array.append(
1967                self.new_optional_fixed_size_interval_var(
1968                    start=starts[i],
1969                    size=sizes[i],
1970                    is_present=are_present[i],
1971                    name=f"{name}[{i}]",
1972                )
1973            )
1974        return pd.Series(index=index, data=interval_array)
1975
1976    def add_no_overlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint:
1977        """Adds NoOverlap(interval_vars).
1978
1979        A NoOverlap constraint ensures that all present intervals do not overlap
1980        in time.
1981
1982        Args:
1983          interval_vars: The list of interval variables to constrain.
1984
1985        Returns:
1986          An instance of the `Constraint` class.
1987        """
1988        ct = Constraint(self)
1989        model_ct = self.__model.constraints[ct.index]
1990        model_ct.no_overlap.intervals.extend(
1991            [self.get_interval_index(x) for x in interval_vars]
1992        )
1993        return ct
1994
1995    def add_no_overlap_2d(
1996        self,
1997        x_intervals: Iterable[IntervalVar],
1998        y_intervals: Iterable[IntervalVar],
1999    ) -> Constraint:
2000        """Adds NoOverlap2D(x_intervals, y_intervals).
2001
2002        A NoOverlap2D constraint ensures that all present rectangles do not overlap
2003        on a plane. Each rectangle is aligned with the X and Y axis, and is defined
2004        by two intervals which represent its projection onto the X and Y axis.
2005
2006        Furthermore, one box is optional if at least one of the x or y interval is
2007        optional.
2008
2009        Args:
2010          x_intervals: The X coordinates of the rectangles.
2011          y_intervals: The Y coordinates of the rectangles.
2012
2013        Returns:
2014          An instance of the `Constraint` class.
2015        """
2016        ct = Constraint(self)
2017        model_ct = self.__model.constraints[ct.index]
2018        model_ct.no_overlap_2d.x_intervals.extend(
2019            [self.get_interval_index(x) for x in x_intervals]
2020        )
2021        model_ct.no_overlap_2d.y_intervals.extend(
2022            [self.get_interval_index(x) for x in y_intervals]
2023        )
2024        return ct
2025
2026    def add_cumulative(
2027        self,
2028        intervals: Iterable[IntervalVar],
2029        demands: Iterable[LinearExprT],
2030        capacity: LinearExprT,
2031    ) -> Constraint:
2032        """Adds Cumulative(intervals, demands, capacity).
2033
2034        This constraint enforces that:
2035
2036            for all t:
2037              sum(demands[i]
2038                if (start(intervals[i]) <= t < end(intervals[i])) and
2039                (intervals[i] is present)) <= capacity
2040
2041        Args:
2042          intervals: The list of intervals.
2043          demands: The list of demands for each interval. Each demand must be >= 0.
2044            Each demand can be a 1-var affine expression (a * x + b).
2045          capacity: The maximum capacity of the cumulative constraint. It can be a
2046            1-var affine expression (a * x + b).
2047
2048        Returns:
2049          An instance of the `Constraint` class.
2050        """
2051        cumulative = Constraint(self)
2052        model_ct = self.__model.constraints[cumulative.index]
2053        model_ct.cumulative.intervals.extend(
2054            [self.get_interval_index(x) for x in intervals]
2055        )
2056        for d in demands:
2057            model_ct.cumulative.demands.append(self.parse_linear_expression(d))
2058        model_ct.cumulative.capacity.CopyFrom(self.parse_linear_expression(capacity))
2059        return cumulative
2060
2061    # Support for model cloning.
2062    def clone(self) -> "CpModel":
2063        """Reset the model, and creates a new one from a CpModelProto instance."""
2064        clone = CpModel()
2065        clone.proto.CopyFrom(self.proto)
2066        clone.rebuild_var_and_constant_map()
2067        return clone
2068
2069    def __copy__(self):
2070        return self.clone()
2071
2072    def __deepcopy__(self, memo):
2073        return self.clone()
2074
2075    def rebuild_var_and_constant_map(self):
2076        """Internal method used during model cloning."""
2077        for i, var in enumerate(self.__model.variables):
2078            if len(var.domain) == 2 and var.domain[0] == var.domain[1]:
2079                self.__constant_map[var.domain[0]] = i
2080            is_boolean = (
2081                len(var.domain) == 2 and var.domain[0] >= 0 and var.domain[1] <= 1
2082            )
2083            self.__var_list.append(IntVar(self.__model, i, is_boolean, None))
2084
2085    def get_bool_var_from_proto_index(self, index: int) -> IntVar:
2086        """Returns an already created Boolean variable from its index."""
2087        result = self._get_int_var(index)
2088        if not result.is_boolean:
2089            raise ValueError(
2090                f"get_bool_var_from_proto_index: index {index} does not reference a"
2091                " boolean variable"
2092            )
2093        return result
2094
2095    def get_int_var_from_proto_index(self, index: int) -> IntVar:
2096        """Returns an already created integer variable from its index."""
2097        return self._get_int_var(index)
2098
2099    def get_interval_var_from_proto_index(self, index: int) -> IntervalVar:
2100        """Returns an already created interval variable from its index."""
2101        if index < 0 or index >= len(self.__model.constraints):
2102            raise ValueError(
2103                f"get_interval_var_from_proto_index: out of bound index {index}"
2104            )
2105        ct = self.__model.constraints[index]
2106        if not ct.HasField("interval"):
2107            raise ValueError(
2108                f"get_interval_var_from_proto_index: index {index} does not"
2109                " reference an" + " interval variable"
2110            )
2111
2112        return IntervalVar(self.__model, self.__var_list, index, None, None, None, None)
2113
2114    # Helpers.
2115
2116    def __str__(self) -> str:
2117        return str(self.__model)
2118
2119    @property
2120    def proto(self) -> cp_model_pb2.CpModelProto:
2121        """Returns the underlying CpModelProto."""
2122        return self.__model
2123
2124    def negated(self, index: int) -> int:
2125        return -index - 1
2126
2127    def get_or_make_index(self, arg: VariableT) -> int:
2128        """Returns the index of a variable, its negation, or a number."""
2129        if isinstance(arg, IntVar):
2130            return arg.index
2131        if isinstance(arg, IntegralTypes):
2132            return self.get_or_make_index_from_constant(arg)
2133        raise TypeError(
2134            f"NotSupported: model.get_or_make_index({type(arg).__name__!r})"
2135        )
2136
2137    def get_or_make_boolean_index(self, arg: LiteralT) -> int:
2138        """Returns an index from a boolean expression."""
2139        if isinstance(arg, IntVar):
2140            self.assert_is_boolean_variable(arg)
2141            return arg.index
2142        if isinstance(arg, cmh.NotBooleanVariable):
2143            self.assert_is_boolean_variable(arg.negated())
2144            return arg.index
2145        if isinstance(arg, IntegralTypes):
2146            if arg == ~False:  # -1
2147                return self.get_or_make_index_from_constant(1)
2148            if arg == ~True:  # -2
2149                return self.get_or_make_index_from_constant(0)
2150            arg = cmn.assert_is_zero_or_one(arg)
2151            return self.get_or_make_index_from_constant(arg)
2152        if cmn.is_boolean(arg):
2153            return self.get_or_make_index_from_constant(int(arg))
2154        raise TypeError(
2155            "not supported:" f" model.get_or_make_boolean_index({type(arg).__name__!r})"
2156        )
2157
2158    def get_interval_index(self, arg: IntervalVar) -> int:
2159        if not isinstance(arg, IntervalVar):
2160            raise TypeError(
2161                f"NotSupported: model.get_interval_index({type(arg).__name__!r})"
2162            )
2163        return arg.index
2164
2165    def get_or_make_index_from_constant(self, value: IntegralT) -> int:
2166        if value in self.__constant_map:
2167            return self.__constant_map[value]
2168        constant_var = self.new_int_var(value, value, "")
2169        self.__constant_map[value] = constant_var.index
2170        return constant_var.index
2171
2172    def parse_linear_expression(
2173        self, linear_expr: LinearExprT, negate: bool = False
2174    ) -> cp_model_pb2.LinearExpressionProto:
2175        """Returns a LinearExpressionProto built from a LinearExpr instance."""
2176        result: cp_model_pb2.LinearExpressionProto = (
2177            cp_model_pb2.LinearExpressionProto()
2178        )
2179        mult = -1 if negate else 1
2180        if isinstance(linear_expr, IntegralTypes):
2181            result.offset = int(linear_expr) * mult
2182            return result
2183
2184        # Raises TypeError if linear_expr is not an integer.
2185        flat_expr = cmh.FlatIntExpr(linear_expr)
2186        result.offset = flat_expr.offset
2187        for var in flat_expr.vars:
2188            result.vars.append(var.index)
2189        for coeff in flat_expr.coeffs:
2190            result.coeffs.append(coeff * mult)
2191        return result
2192
2193    def _set_objective(self, obj: ObjLinearExprT, minimize: bool):
2194        """Sets the objective of the model."""
2195        self.clear_objective()
2196        if isinstance(obj, IntegralTypes):
2197            self.__model.objective.offset = int(obj)
2198            self.__model.objective.scaling_factor = 1.0
2199        elif isinstance(obj, LinearExpr):
2200            if obj.is_integer():
2201                int_obj = cmh.FlatIntExpr(obj)
2202                for var in int_obj.vars:
2203                    self.__model.objective.vars.append(var.index)
2204                if minimize:
2205                    self.__model.objective.scaling_factor = 1.0
2206                    self.__model.objective.offset = int_obj.offset
2207                    self.__model.objective.coeffs.extend(int_obj.coeffs)
2208                else:
2209                    self.__model.objective.scaling_factor = -1.0
2210                    self.__model.objective.offset = -int_obj.offset
2211                    for c in int_obj.coeffs:
2212                        self.__model.objective.coeffs.append(-c)
2213            else:
2214                float_obj = cmh.FlatFloatExpr(obj)
2215                for var in float_obj.vars:
2216                    self.__model.floating_point_objective.vars.append(var.index)
2217                self.__model.floating_point_objective.coeffs.extend(float_obj.coeffs)
2218                self.__model.floating_point_objective.maximize = not minimize
2219                self.__model.floating_point_objective.offset = float_obj.offset
2220        else:
2221            raise TypeError(
2222                f"TypeError: {type(obj).__name__!r} is not a valid objective"
2223            )
2224
2225    def minimize(self, obj: ObjLinearExprT):
2226        """Sets the objective of the model to minimize(obj)."""
2227        self._set_objective(obj, minimize=True)
2228
2229    def maximize(self, obj: ObjLinearExprT):
2230        """Sets the objective of the model to maximize(obj)."""
2231        self._set_objective(obj, minimize=False)
2232
2233    def has_objective(self) -> bool:
2234        return self.__model.HasField("objective") or self.__model.HasField(
2235            "floating_point_objective"
2236        )
2237
2238    def clear_objective(self):
2239        self.__model.ClearField("objective")
2240        self.__model.ClearField("floating_point_objective")
2241
2242    def add_decision_strategy(
2243        self,
2244        variables: Sequence[IntVar],
2245        var_strategy: cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy,
2246        domain_strategy: cp_model_pb2.DecisionStrategyProto.DomainReductionStrategy,
2247    ) -> None:
2248        """Adds a search strategy to the model.
2249
2250        Args:
2251          variables: a list of variables this strategy will assign.
2252          var_strategy: heuristic to choose the next variable to assign.
2253          domain_strategy: heuristic to reduce the domain of the selected variable.
2254            Currently, this is advanced code: the union of all strategies added to
2255            the model must be complete, i.e. instantiates all variables. Otherwise,
2256            solve() will fail.
2257        """
2258
2259        strategy: cp_model_pb2.DecisionStrategyProto = (
2260            self.__model.search_strategy.add()
2261        )
2262        for v in variables:
2263            expr = strategy.exprs.add()
2264            if v.index >= 0:
2265                expr.vars.append(v.index)
2266                expr.coeffs.append(1)
2267            else:
2268                expr.vars.append(self.negated(v.index))
2269                expr.coeffs.append(-1)
2270                expr.offset = 1
2271
2272        strategy.variable_selection_strategy = var_strategy
2273        strategy.domain_reduction_strategy = domain_strategy
2274
2275    def model_stats(self) -> str:
2276        """Returns a string containing some model statistics."""
2277        return cmh.CpSatHelper.model_stats(self.__model)
2278
2279    def validate(self) -> str:
2280        """Returns a string indicating that the model is invalid."""
2281        return cmh.CpSatHelper.validate_model(self.__model)
2282
2283    def export_to_file(self, file: str) -> bool:
2284        """Write the model as a protocol buffer to 'file'.
2285
2286        Args:
2287          file: file to write the model to. If the filename ends with 'txt', the
2288            model will be written as a text file, otherwise, the binary format will
2289            be used.
2290
2291        Returns:
2292          True if the model was correctly written.
2293        """
2294        return cmh.CpSatHelper.write_model_to_file(self.__model, file)
2295
2296    @overload
2297    def add_hint(self, var: IntVar, value: int) -> None: ...
2298
2299    @overload
2300    def add_hint(self, literal: BoolVarT, value: bool) -> None: ...
2301
2302    def add_hint(self, var, value) -> None:
2303        """Adds 'var == value' as a hint to the solver."""
2304        if var.index >= 0:
2305            self.__model.solution_hint.vars.append(self.get_or_make_index(var))
2306            self.__model.solution_hint.values.append(int(value))
2307        else:
2308            self.__model.solution_hint.vars.append(self.negated(var.index))
2309            self.__model.solution_hint.values.append(int(not value))
2310
2311    def clear_hints(self):
2312        """Removes any solution hint from the model."""
2313        self.__model.ClearField("solution_hint")
2314
2315    def add_assumption(self, lit: LiteralT) -> None:
2316        """Adds the literal to the model as assumptions."""
2317        self.__model.assumptions.append(self.get_or_make_boolean_index(lit))
2318
2319    def add_assumptions(self, literals: Iterable[LiteralT]) -> None:
2320        """Adds the literals to the model as assumptions."""
2321        for lit in literals:
2322            self.add_assumption(lit)
2323
2324    def clear_assumptions(self) -> None:
2325        """Removes all assumptions from the model."""
2326        self.__model.ClearField("assumptions")
2327
2328    # Helpers.
2329    def assert_is_boolean_variable(self, x: LiteralT) -> None:
2330        if isinstance(x, IntVar):
2331            var = self.__model.variables[x.index]
2332            if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1:
2333                raise TypeError(
2334                    f"TypeError: {type(x).__name__!r} is not a boolean variable"
2335                )
2336        elif not isinstance(x, cmh.NotBooleanVariable):
2337            raise TypeError(
2338                f"TypeError: {type(x).__name__!r}  is not a boolean variable"
2339            )
2340
2341    # Compatibility with pre PEP8
2342    # pylint: disable=invalid-name
2343
2344    def Name(self) -> str:
2345        return self.name
2346
2347    def SetName(self, name: str) -> None:
2348        self.name = name
2349
2350    def Proto(self) -> cp_model_pb2.CpModelProto:
2351        return self.proto
2352
2353    NewIntVar = new_int_var
2354    NewIntVarFromDomain = new_int_var_from_domain
2355    NewBoolVar = new_bool_var
2356    NewConstant = new_constant
2357    NewIntVarSeries = new_int_var_series
2358    NewBoolVarSeries = new_bool_var_series
2359    AddLinearConstraint = add_linear_constraint
2360    AddLinearExpressionInDomain = add_linear_expression_in_domain
2361    Add = add
2362    AddAllDifferent = add_all_different
2363    AddElement = add_element
2364    AddCircuit = add_circuit
2365    AddMultipleCircuit = add_multiple_circuit
2366    AddAllowedAssignments = add_allowed_assignments
2367    AddForbiddenAssignments = add_forbidden_assignments
2368    AddAutomaton = add_automaton
2369    AddInverse = add_inverse
2370    AddReservoirConstraint = add_reservoir_constraint
2371    AddReservoirConstraintWithActive = add_reservoir_constraint_with_active
2372    AddImplication = add_implication
2373    AddBoolOr = add_bool_or
2374    AddAtLeastOne = add_at_least_one
2375    AddAtMostOne = add_at_most_one
2376    AddExactlyOne = add_exactly_one
2377    AddBoolAnd = add_bool_and
2378    AddBoolXOr = add_bool_xor
2379    AddMinEquality = add_min_equality
2380    AddMaxEquality = add_max_equality
2381    AddDivisionEquality = add_division_equality
2382    AddAbsEquality = add_abs_equality
2383    AddModuloEquality = add_modulo_equality
2384    AddMultiplicationEquality = add_multiplication_equality
2385    NewIntervalVar = new_interval_var
2386    NewIntervalVarSeries = new_interval_var_series
2387    NewFixedSizeIntervalVar = new_fixed_size_interval_var
2388    NewOptionalIntervalVar = new_optional_interval_var
2389    NewOptionalIntervalVarSeries = new_optional_interval_var_series
2390    NewOptionalFixedSizeIntervalVar = new_optional_fixed_size_interval_var
2391    NewOptionalFixedSizeIntervalVarSeries = new_optional_fixed_size_interval_var_series
2392    AddNoOverlap = add_no_overlap
2393    AddNoOverlap2D = add_no_overlap_2d
2394    AddCumulative = add_cumulative
2395    Clone = clone
2396    GetBoolVarFromProtoIndex = get_bool_var_from_proto_index
2397    GetIntVarFromProtoIndex = get_int_var_from_proto_index
2398    GetIntervalVarFromProtoIndex = get_interval_var_from_proto_index
2399    Minimize = minimize
2400    Maximize = maximize
2401    HasObjective = has_objective
2402    ClearObjective = clear_objective
2403    AddDecisionStrategy = add_decision_strategy
2404    ModelStats = model_stats
2405    Validate = validate
2406    ExportToFile = export_to_file
2407    AddHint = add_hint
2408    ClearHints = clear_hints
2409    AddAssumption = add_assumption
2410    AddAssumptions = add_assumptions
2411    ClearAssumptions = clear_assumptions
2412
2413    # pylint: enable=invalid-name

Methods for building a CP model.

Methods beginning with:

  • New create integer, boolean, or interval variables.
  • add create new constraints and add them to the model.
name: str
636    @property
637    def name(self) -> str:
638        """Returns the name of the model."""
639        if not self.__model or not self.__model.name:
640            return ""
641        return self.__model.name

Returns the name of the model.

def rebuild_from_linear_expression_proto( self, proto: ortools.sat.cp_model_pb2.LinearExpressionProto) -> LinearExpr:
658    def rebuild_from_linear_expression_proto(
659        self,
660        proto: cp_model_pb2.LinearExpressionProto,
661    ) -> LinearExpr:
662        return self.__var_list.rebuild_expr(proto)
def new_int_var( self, lb: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], ub: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], name: str) -> IntVar:
664    def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar:
665        """Create an integer variable with domain [lb, ub].
666
667        The CP-SAT solver is limited to integer variables. If you have fractional
668        values, scale them up so that they become integers; if you have strings,
669        encode them as integers.
670
671        Args:
672          lb: Lower bound for the variable.
673          ub: Upper bound for the variable.
674          name: The name of the variable.
675
676        Returns:
677          a variable whose domain is [lb, ub].
678        """
679        domain_is_boolean = lb >= 0 and ub <= 1
680        return self._append_int_var(
681            IntVar(
682                self.__model,
683                sorted_interval_list.Domain(lb, ub),
684                domain_is_boolean,
685                name,
686            )
687        )

Create an integer variable with domain [lb, ub].

The CP-SAT solver is limited to integer variables. If you have fractional values, scale them up so that they become integers; if you have strings, encode them as integers.

Arguments:
  • lb: Lower bound for the variable.
  • ub: Upper bound for the variable.
  • name: The name of the variable.
Returns:

a variable whose domain is [lb, ub].

def new_int_var_from_domain( self, domain: Domain, name: str) -> IntVar:
689    def new_int_var_from_domain(
690        self, domain: sorted_interval_list.Domain, name: str
691    ) -> IntVar:
692        """Create an integer variable from a domain.
693
694        A domain is a set of integers specified by a collection of intervals.
695        For example, `model.new_int_var_from_domain(cp_model.
696             Domain.from_intervals([[1, 2], [4, 6]]), 'x')`
697
698        Args:
699          domain: An instance of the Domain class.
700          name: The name of the variable.
701
702        Returns:
703            a variable whose domain is the given domain.
704        """
705        domain_is_boolean = domain.min() >= 0 and domain.max() <= 1
706        return self._append_int_var(
707            IntVar(self.__model, domain, domain_is_boolean, name)
708        )

Create an integer variable from a domain.

A domain is a set of integers specified by a collection of intervals. For example, model.new_int_var_from_domain(cp_model. Domain.from_intervals([[1, 2], [4, 6]]), 'x')

Arguments:
  • domain: An instance of the Domain class.
  • name: The name of the variable.
Returns:

a variable whose domain is the given domain.

def new_bool_var(self, name: str) -> IntVar:
710    def new_bool_var(self, name: str) -> IntVar:
711        """Creates a 0-1 variable with the given name."""
712        return self._append_int_var(
713            IntVar(self.__model, sorted_interval_list.Domain(0, 1), True, name)
714        )

Creates a 0-1 variable with the given name.

def new_constant( self, value: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> IntVar:
716    def new_constant(self, value: IntegralT) -> IntVar:
717        """Declares a constant integer."""
718        index: int = self.get_or_make_index_from_constant(value)
719        return self._get_int_var(index)

Declares a constant integer.

def new_int_var_series( self, name: str, index: pandas.core.indexes.base.Index, lower_bounds: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], upper_bounds: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series]) -> pandas.core.series.Series:
721    def new_int_var_series(
722        self,
723        name: str,
724        index: pd.Index,
725        lower_bounds: Union[IntegralT, pd.Series],
726        upper_bounds: Union[IntegralT, pd.Series],
727    ) -> pd.Series:
728        """Creates a series of (scalar-valued) variables with the given name.
729
730        Args:
731          name (str): Required. The name of the variable set.
732          index (pd.Index): Required. The index to use for the variable set.
733          lower_bounds (Union[int, pd.Series]): A lower bound for variables in the
734            set. If a `pd.Series` is passed in, it will be based on the
735            corresponding values of the pd.Series.
736          upper_bounds (Union[int, pd.Series]): An upper bound for variables in the
737            set. If a `pd.Series` is passed in, it will be based on the
738            corresponding values of the pd.Series.
739
740        Returns:
741          pd.Series: The variable set indexed by its corresponding dimensions.
742
743        Raises:
744          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
745          ValueError: if the `name` is not a valid identifier or already exists.
746          ValueError: if the `lowerbound` is greater than the `upperbound`.
747          ValueError: if the index of `lower_bound`, or `upper_bound` does not match
748          the input index.
749        """
750        if not isinstance(index, pd.Index):
751            raise TypeError("Non-index object is used as index")
752        if not name.isidentifier():
753            raise ValueError(f"name={name!r} is not a valid identifier")
754        if (
755            isinstance(lower_bounds, IntegralTypes)
756            and isinstance(upper_bounds, IntegralTypes)
757            and lower_bounds > upper_bounds
758        ):
759            raise ValueError(
760                f"lower_bound={lower_bounds} is greater than"
761                f" upper_bound={upper_bounds} for variable set={name}"
762            )
763
764        lower_bounds = _convert_to_integral_series_and_validate_index(
765            lower_bounds, index
766        )
767        upper_bounds = _convert_to_integral_series_and_validate_index(
768            upper_bounds, index
769        )
770        return pd.Series(
771            index=index,
772            data=[
773                # pylint: disable=g-complex-comprehension
774                self._append_int_var(
775                    IntVar(
776                        model=self.__model,
777                        name=f"{name}[{i}]",
778                        domain=sorted_interval_list.Domain(
779                            lower_bounds[i], upper_bounds[i]
780                        ),
781                        is_boolean=lower_bounds[i] >= 0 and upper_bounds[i] <= 1,
782                    )
783                )
784                for i in index
785            ],
786        )

Creates a series of (scalar-valued) variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • lower_bounds (Union[int, pd.Series]): A lower bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • upper_bounds (Union[int, pd.Series]): An upper bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the lowerbound is greater than the upperbound.
  • ValueError: if the index of lower_bound, or upper_bound does not match
  • the input index.
def new_bool_var_series( self, name: str, index: pandas.core.indexes.base.Index) -> pandas.core.series.Series:
788    def new_bool_var_series(
789        self,
790        name: str,
791        index: pd.Index,
792    ) -> pd.Series:
793        """Creates a series of (scalar-valued) variables with the given name.
794
795        Args:
796          name (str): Required. The name of the variable set.
797          index (pd.Index): Required. The index to use for the variable set.
798
799        Returns:
800          pd.Series: The variable set indexed by its corresponding dimensions.
801
802        Raises:
803          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
804          ValueError: if the `name` is not a valid identifier or already exists.
805        """
806        if not isinstance(index, pd.Index):
807            raise TypeError("Non-index object is used as index")
808        if not name.isidentifier():
809            raise ValueError(f"name={name!r} is not a valid identifier")
810        return pd.Series(
811            index=index,
812            data=[
813                # pylint: disable=g-complex-comprehension
814                self._append_int_var(
815                    IntVar(
816                        model=self.__model,
817                        name=f"{name}[{i}]",
818                        domain=sorted_interval_list.Domain(0, 1),
819                        is_boolean=True,
820                    )
821                )
822                for i in index
823            ],
824        )

Creates a series of (scalar-valued) variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
Returns:

pd.Series: The variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
def add_linear_constraint( self, linear_expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], lb: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], ub: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
828    def add_linear_constraint(
829        self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT
830    ) -> Constraint:
831        """Adds the constraint: `lb <= linear_expr <= ub`."""
832        return self.add_linear_expression_in_domain(
833            linear_expr, sorted_interval_list.Domain(lb, ub)
834        )

Adds the constraint: lb <= linear_expr <= ub.

def add_linear_expression_in_domain( self, linear_expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], domain: Domain) -> Constraint:
836    def add_linear_expression_in_domain(
837        self,
838        linear_expr: LinearExprT,
839        domain: sorted_interval_list.Domain,
840    ) -> Constraint:
841        """Adds the constraint: `linear_expr` in `domain`."""
842        if isinstance(linear_expr, LinearExpr):
843            ble = BoundedLinearExpression(linear_expr, domain)
844            if not ble.ok:
845                raise TypeError(
846                    "Cannot add a linear expression containing floating point"
847                    f" coefficients or constants: {type(linear_expr).__name__!r}"
848                )
849            return self.add(ble)
850        if isinstance(linear_expr, IntegralTypes):
851            if not domain.contains(int(linear_expr)):
852                return self.add_bool_or([])  # Evaluate to false.
853            else:
854                return self.add_bool_and([])  # Evaluate to true.
855        raise TypeError(
856            "not supported:"
857            f" CpModel.add_linear_expression_in_domain({type(linear_expr).__name__!r})"
858        )

Adds the constraint: linear_expr in domain.

def add( self, ct: Union[BoundedLinearExpression, bool, numpy.bool]) -> Constraint:
860    def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint:
861        """Adds a `BoundedLinearExpression` to the model.
862
863        Args:
864          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
865
866        Returns:
867          An instance of the `Constraint` class.
868
869        Raises:
870          TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean.
871        """
872        if isinstance(ct, BoundedLinearExpression):
873            result = Constraint(self)
874            model_ct = self.__model.constraints[result.index]
875            for var in ct.vars:
876                model_ct.linear.vars.append(var.index)
877            model_ct.linear.coeffs.extend(ct.coeffs)
878            model_ct.linear.domain.extend(
879                [
880                    cmn.capped_subtraction(x, ct.offset)
881                    for x in ct.bounds.flattened_intervals()
882                ]
883            )
884            return result
885        if ct and cmn.is_boolean(ct):
886            return self.add_bool_or([True])
887        if not ct and cmn.is_boolean(ct):
888            return self.add_bool_or([])  # Evaluate to false.
889        raise TypeError(f"not supported: CpModel.add({type(ct).__name__!r})")

Adds a BoundedLinearExpression to the model.

Arguments:
Returns:

An instance of the Constraint class.

Raises:
def add_all_different(self, *expressions):
899    def add_all_different(self, *expressions):
900        """Adds AllDifferent(expressions).
901
902        This constraint forces all expressions to have different values.
903
904        Args:
905          *expressions: simple expressions of the form a * var + constant.
906
907        Returns:
908          An instance of the `Constraint` class.
909        """
910        ct = Constraint(self)
911        model_ct = self.__model.constraints[ct.index]
912        expanded = expand_generator_or_tuple(expressions)
913        model_ct.all_diff.exprs.extend(
914            self.parse_linear_expression(x) for x in expanded
915        )
916        return ct

Adds AllDifferent(expressions).

This constraint forces all expressions to have different values.

Arguments:
  • *expressions: simple expressions of the form a * var + constant.
Returns:

An instance of the Constraint class.

def add_element( self, index: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], expressions: Sequence[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
918    def add_element(
919        self,
920        index: LinearExprT,
921        expressions: Sequence[LinearExprT],
922        target: LinearExprT,
923    ) -> Constraint:
924        """Adds the element constraint: `expressions[index] == target`.
925
926        Args:
927          index: The index of the selected expression in the array. It must be an
928            affine expression (a * var + b).
929          expressions: A list of affine expressions.
930          target: The expression constrained to be equal to the selected expression.
931            It must be an affine expression (a * var + b).
932
933        Returns:
934          An instance of the `Constraint` class.
935        """
936
937        if not expressions:
938            raise ValueError("add_element expects a non-empty expressions array")
939
940        if isinstance(index, IntegralTypes):
941            expression: LinearExprT = list(expressions)[int(index)]
942            return self.add(expression == target)
943
944        ct = Constraint(self)
945        model_ct = self.__model.constraints[ct.index]
946        model_ct.element.linear_index.CopyFrom(self.parse_linear_expression(index))
947        model_ct.element.exprs.extend(
948            [self.parse_linear_expression(e) for e in expressions]
949        )
950        model_ct.element.linear_target.CopyFrom(self.parse_linear_expression(target))
951        return ct

Adds the element constraint: expressions[index] == target.

Arguments:
  • index: The index of the selected expression in the array. It must be an affine expression (a * var + b).
  • expressions: A list of affine expressions.
  • target: The expression constrained to be equal to the selected expression. It must be an affine expression (a * var + b).
Returns:

An instance of the Constraint class.

def add_circuit( self, arcs: Sequence[Tuple[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]]]) -> Constraint:
953    def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
954        """Adds Circuit(arcs).
955
956        Adds a circuit constraint from a sparse list of arcs that encode the graph.
957
958        A circuit is a unique Hamiltonian cycle in a subgraph of the total
959        graph. In case a node 'i' is not in the cycle, then there must be a
960        loop arc 'i -> i' associated with a true literal. Otherwise
961        this constraint will fail.
962
963        Args:
964          arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
965            literal). The arc is selected in the circuit if the literal is true.
966            Both source_node and destination_node must be integers between 0 and the
967            number of nodes - 1.
968
969        Returns:
970          An instance of the `Constraint` class.
971
972        Raises:
973          ValueError: If the list of arcs is empty.
974        """
975        if not arcs:
976            raise ValueError("add_circuit expects a non-empty array of arcs")
977        ct = Constraint(self)
978        model_ct = self.__model.constraints[ct.index]
979        for arc in arcs:
980            model_ct.circuit.tails.append(arc[0])
981            model_ct.circuit.heads.append(arc[1])
982            model_ct.circuit.literals.append(self.get_or_make_boolean_index(arc[2]))
983        return ct

Adds Circuit(arcs).

Adds a circuit constraint from a sparse list of arcs that encode the graph.

A circuit is a unique Hamiltonian cycle in a subgraph of the total graph. In case a node 'i' is not in the cycle, then there must be a loop arc 'i -> i' associated with a true literal. Otherwise this constraint will fail.

Arguments:
  • arcs: a list of arcs. An arc is a tuple (source_node, destination_node, literal). The arc is selected in the circuit if the literal is true. Both source_node and destination_node must be integers between 0 and the number of nodes - 1.
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: If the list of arcs is empty.
def add_multiple_circuit( self, arcs: Sequence[Tuple[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]]]) -> Constraint:
 985    def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
 986        """Adds a multiple circuit constraint, aka the 'VRP' constraint.
 987
 988        The direct graph where arc #i (from tails[i] to head[i]) is present iff
 989        literals[i] is true must satisfy this set of properties:
 990        - #incoming arcs == 1 except for node 0.
 991        - #outgoing arcs == 1 except for node 0.
 992        - for node zero, #incoming arcs == #outgoing arcs.
 993        - There are no duplicate arcs.
 994        - Self-arcs are allowed except for node 0.
 995        - There is no cycle in this graph, except through node 0.
 996
 997        Args:
 998          arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
 999            literal). The arc is selected in the circuit if the literal is true.
1000            Both source_node and destination_node must be integers between 0 and the
1001            number of nodes - 1.
1002
1003        Returns:
1004          An instance of the `Constraint` class.
1005
1006        Raises:
1007          ValueError: If the list of arcs is empty.
1008        """
1009        if not arcs:
1010            raise ValueError("add_multiple_circuit expects a non-empty array of arcs")
1011        ct = Constraint(self)
1012        model_ct = self.__model.constraints[ct.index]
1013        for arc in arcs:
1014            model_ct.routes.tails.append(arc[0])
1015            model_ct.routes.heads.append(arc[1])
1016            model_ct.routes.literals.append(self.get_or_make_boolean_index(arc[2]))
1017        return ct

Adds a multiple circuit constraint, aka the 'VRP' constraint.

The direct graph where arc #i (from tails[i] to head[i]) is present iff literals[i] is true must satisfy this set of properties:

  • #incoming arcs == 1 except for node 0.
  • #outgoing arcs == 1 except for node 0.
  • for node zero, #incoming arcs == #outgoing arcs.
  • There are no duplicate arcs.
  • Self-arcs are allowed except for node 0.
  • There is no cycle in this graph, except through node 0.
Arguments:
  • arcs: a list of arcs. An arc is a tuple (source_node, destination_node, literal). The arc is selected in the circuit if the literal is true. Both source_node and destination_node must be integers between 0 and the number of nodes - 1.
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: If the list of arcs is empty.
def add_allowed_assignments( self, expressions: Sequence[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], tuples_list: Iterable[Sequence[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]]) -> Constraint:
1019    def add_allowed_assignments(
1020        self,
1021        expressions: Sequence[LinearExprT],
1022        tuples_list: Iterable[Sequence[IntegralT]],
1023    ) -> Constraint:
1024        """Adds AllowedAssignments(expressions, tuples_list).
1025
1026        An AllowedAssignments constraint is a constraint on an array of affine
1027        expressions, which requires that when all expressions are assigned values,
1028        the
1029        resulting array equals one of the  tuples in `tuple_list`.
1030
1031        Args:
1032          expressions: A list of affine expressions (a * var + b).
1033          tuples_list: A list of admissible tuples. Each tuple must have the same
1034            length as the expressions, and the ith value of a tuple corresponds to
1035            the ith expression.
1036
1037        Returns:
1038          An instance of the `Constraint` class.
1039
1040        Raises:
1041          TypeError: If a tuple does not have the same size as the list of
1042              expressions.
1043          ValueError: If the array of expressions is empty.
1044        """
1045
1046        if not expressions:
1047            raise ValueError(
1048                "add_allowed_assignments expects a non-empty expressions array"
1049            )
1050
1051        ct: Constraint = Constraint(self)
1052        model_ct = self.__model.constraints[ct.index]
1053        model_ct.table.exprs.extend(
1054            [self.parse_linear_expression(e) for e in expressions]
1055        )
1056        arity: int = len(expressions)
1057        for one_tuple in tuples_list:
1058            if len(one_tuple) != arity:
1059                raise TypeError(f"Tuple {one_tuple!r} has the wrong arity")
1060
1061        # duck-typing (no explicit type checks here)
1062        try:
1063            for one_tuple in tuples_list:
1064                model_ct.table.values.extend(one_tuple)
1065        except ValueError as ex:
1066            raise TypeError(
1067                "add_xxx_assignment: Not an integer or does not fit in an int64_t:"
1068                f" {type(ex.args).__name__!r}"
1069            ) from ex
1070
1071        return ct

Adds AllowedAssignments(expressions, tuples_list).

An AllowedAssignments constraint is a constraint on an array of affine expressions, which requires that when all expressions are assigned values, the resulting array equals one of the tuples in tuple_list.

Arguments:
  • expressions: A list of affine expressions (a * var + b).
  • tuples_list: A list of admissible tuples. Each tuple must have the same length as the expressions, and the ith value of a tuple corresponds to the ith expression.
Returns:

An instance of the Constraint class.

Raises:
  • TypeError: If a tuple does not have the same size as the list of expressions.
  • ValueError: If the array of expressions is empty.
def add_forbidden_assignments( self, expressions: Sequence[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], tuples_list: Iterable[Sequence[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]]) -> Constraint:
1073    def add_forbidden_assignments(
1074        self,
1075        expressions: Sequence[LinearExprT],
1076        tuples_list: Iterable[Sequence[IntegralT]],
1077    ) -> Constraint:
1078        """Adds add_forbidden_assignments(expressions, [tuples_list]).
1079
1080        A ForbiddenAssignments constraint is a constraint on an array of affine
1081        expressions where the list of impossible combinations is provided in the
1082        tuples list.
1083
1084        Args:
1085          expressions: A list of affine expressions (a * var + b).
1086          tuples_list: A list of forbidden tuples. Each tuple must have the same
1087            length as the expressions, and the *i*th value of a tuple corresponds to
1088            the *i*th expression.
1089
1090        Returns:
1091          An instance of the `Constraint` class.
1092
1093        Raises:
1094          TypeError: If a tuple does not have the same size as the list of
1095                     expressions.
1096          ValueError: If the array of expressions is empty.
1097        """
1098
1099        if not expressions:
1100            raise ValueError(
1101                "add_forbidden_assignments expects a non-empty expressions array"
1102            )
1103
1104        index: int = len(self.__model.constraints)
1105        ct: Constraint = self.add_allowed_assignments(expressions, tuples_list)
1106        self.__model.constraints[index].table.negated = True
1107        return ct

Adds add_forbidden_assignments(expressions, [tuples_list]).

A ForbiddenAssignments constraint is a constraint on an array of affine expressions where the list of impossible combinations is provided in the tuples list.

Arguments:
  • expressions: A list of affine expressions (a * var + b).
  • tuples_list: A list of forbidden tuples. Each tuple must have the same length as the expressions, and the ith value of a tuple corresponds to the ith expression.
Returns:

An instance of the Constraint class.

Raises:
  • TypeError: If a tuple does not have the same size as the list of expressions.
  • ValueError: If the array of expressions is empty.
def add_automaton( self, transition_expressions: Sequence[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], starting_state: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], final_states: Sequence[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], transition_triples: Sequence[Tuple[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]]) -> Constraint:
1109    def add_automaton(
1110        self,
1111        transition_expressions: Sequence[LinearExprT],
1112        starting_state: IntegralT,
1113        final_states: Sequence[IntegralT],
1114        transition_triples: Sequence[Tuple[IntegralT, IntegralT, IntegralT]],
1115    ) -> Constraint:
1116        """Adds an automaton constraint.
1117
1118        An automaton constraint takes a list of affine expressions (a * var + b) (of
1119        size *n*), an initial state, a set of final states, and a set of
1120        transitions. A transition is a triplet (*tail*, *transition*, *head*), where
1121        *tail* and *head* are states, and *transition* is the label of an arc from
1122        *head* to *tail*, corresponding to the value of one expression in the list
1123        of
1124        expressions.
1125
1126        This automaton will be unrolled into a flow with *n* + 1 phases. Each phase
1127        contains the possible states of the automaton. The first state contains the
1128        initial state. The last phase contains the final states.
1129
1130        Between two consecutive phases *i* and *i* + 1, the automaton creates a set
1131        of arcs. For each transition (*tail*, *transition*, *head*), it will add
1132        an arc from the state *tail* of phase *i* and the state *head* of phase
1133        *i* + 1. This arc is labeled by the value *transition* of the expression
1134        `expressions[i]`. That is, this arc can only be selected if `expressions[i]`
1135        is assigned the value *transition*.
1136
1137        A feasible solution of this constraint is an assignment of expressions such
1138        that, starting from the initial state in phase 0, there is a path labeled by
1139        the values of the expressions that ends in one of the final states in the
1140        final phase.
1141
1142        Args:
1143          transition_expressions: A non-empty list of affine expressions (a * var +
1144            b) whose values correspond to the labels of the arcs traversed by the
1145            automaton.
1146          starting_state: The initial state of the automaton.
1147          final_states: A non-empty list of admissible final states.
1148          transition_triples: A list of transitions for the automaton, in the
1149            following format (current_state, variable_value, next_state).
1150
1151        Returns:
1152          An instance of the `Constraint` class.
1153
1154        Raises:
1155          ValueError: if `transition_expressions`, `final_states`, or
1156            `transition_triples` are empty.
1157        """
1158
1159        if not transition_expressions:
1160            raise ValueError(
1161                "add_automaton expects a non-empty transition_expressions array"
1162            )
1163        if not final_states:
1164            raise ValueError("add_automaton expects some final states")
1165
1166        if not transition_triples:
1167            raise ValueError("add_automaton expects some transition triples")
1168
1169        ct = Constraint(self)
1170        model_ct = self.__model.constraints[ct.index]
1171        model_ct.automaton.exprs.extend(
1172            [self.parse_linear_expression(e) for e in transition_expressions]
1173        )
1174        model_ct.automaton.starting_state = starting_state
1175        for v in final_states:
1176            model_ct.automaton.final_states.append(v)
1177        for t in transition_triples:
1178            if len(t) != 3:
1179                raise TypeError(f"Tuple {t!r} has the wrong arity (!= 3)")
1180            model_ct.automaton.transition_tail.append(t[0])
1181            model_ct.automaton.transition_label.append(t[1])
1182            model_ct.automaton.transition_head.append(t[2])
1183        return ct

Adds an automaton constraint.

An automaton constraint takes a list of affine expressions (a * var + b) (of size n), an initial state, a set of final states, and a set of transitions. A transition is a triplet (tail, transition, head), where tail and head are states, and transition is the label of an arc from head to tail, corresponding to the value of one expression in the list of expressions.

This automaton will be unrolled into a flow with n + 1 phases. Each phase contains the possible states of the automaton. The first state contains the initial state. The last phase contains the final states.

Between two consecutive phases i and i + 1, the automaton creates a set of arcs. For each transition (tail, transition, head), it will add an arc from the state tail of phase i and the state head of phase i + 1. This arc is labeled by the value transition of the expression expressions[i]. That is, this arc can only be selected if expressions[i] is assigned the value transition.

A feasible solution of this constraint is an assignment of expressions such that, starting from the initial state in phase 0, there is a path labeled by the values of the expressions that ends in one of the final states in the final phase.

Arguments:
  • transition_expressions: A non-empty list of affine expressions (a * var + b) whose values correspond to the labels of the arcs traversed by the automaton.
  • starting_state: The initial state of the automaton.
  • final_states: A non-empty list of admissible final states.
  • transition_triples: A list of transitions for the automaton, in the following format (current_state, variable_value, next_state).
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: if transition_expressions, final_states, or transition_triples are empty.
def add_inverse( self, variables: Sequence[Union[IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], inverse_variables: Sequence[Union[IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]) -> Constraint:
1185    def add_inverse(
1186        self,
1187        variables: Sequence[VariableT],
1188        inverse_variables: Sequence[VariableT],
1189    ) -> Constraint:
1190        """Adds Inverse(variables, inverse_variables).
1191
1192        An inverse constraint enforces that if `variables[i]` is assigned a value
1193        `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa.
1194
1195        Args:
1196          variables: An array of integer variables.
1197          inverse_variables: An array of integer variables.
1198
1199        Returns:
1200          An instance of the `Constraint` class.
1201
1202        Raises:
1203          TypeError: if variables and inverse_variables have different lengths, or
1204              if they are empty.
1205        """
1206
1207        if not variables or not inverse_variables:
1208            raise TypeError("The Inverse constraint does not accept empty arrays")
1209        if len(variables) != len(inverse_variables):
1210            raise TypeError(
1211                "In the inverse constraint, the two array variables and"
1212                " inverse_variables must have the same length."
1213            )
1214        ct = Constraint(self)
1215        model_ct = self.__model.constraints[ct.index]
1216        model_ct.inverse.f_direct.extend([self.get_or_make_index(x) for x in variables])
1217        model_ct.inverse.f_inverse.extend(
1218            [self.get_or_make_index(x) for x in inverse_variables]
1219        )
1220        return ct

Adds Inverse(variables, inverse_variables).

An inverse constraint enforces that if variables[i] is assigned a value j, then inverse_variables[j] is assigned a value i. And vice versa.

Arguments:
  • variables: An array of integer variables.
  • inverse_variables: An array of integer variables.
Returns:

An instance of the Constraint class.

Raises:
  • TypeError: if variables and inverse_variables have different lengths, or if they are empty.
def add_reservoir_constraint( self, times: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], level_changes: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], min_level: int, max_level: int) -> Constraint:
1222    def add_reservoir_constraint(
1223        self,
1224        times: Iterable[LinearExprT],
1225        level_changes: Iterable[LinearExprT],
1226        min_level: int,
1227        max_level: int,
1228    ) -> Constraint:
1229        """Adds Reservoir(times, level_changes, min_level, max_level).
1230
1231        Maintains a reservoir level within bounds. The water level starts at 0, and
1232        at any time, it must be between min_level and max_level.
1233
1234        If the affine expression `times[i]` is assigned a value t, then the current
1235        level changes by `level_changes[i]`, which is constant, at time t.
1236
1237         Note that min level must be <= 0, and the max level must be >= 0. Please
1238         use fixed level_changes to simulate initial state.
1239
1240         Therefore, at any time:
1241             sum(level_changes[i] if times[i] <= t) in [min_level, max_level]
1242
1243        Args:
1244          times: A list of 1-var affine expressions (a * x + b) which specify the
1245            time of the filling or emptying the reservoir.
1246          level_changes: A list of integer values that specifies the amount of the
1247            emptying or filling. Currently, variable demands are not supported.
1248          min_level: At any time, the level of the reservoir must be greater or
1249            equal than the min level.
1250          max_level: At any time, the level of the reservoir must be less or equal
1251            than the max level.
1252
1253        Returns:
1254          An instance of the `Constraint` class.
1255
1256        Raises:
1257          ValueError: if max_level < min_level.
1258
1259          ValueError: if max_level < 0.
1260
1261          ValueError: if min_level > 0
1262        """
1263
1264        if max_level < min_level:
1265            raise ValueError("Reservoir constraint must have a max_level >= min_level")
1266
1267        if max_level < 0:
1268            raise ValueError("Reservoir constraint must have a max_level >= 0")
1269
1270        if min_level > 0:
1271            raise ValueError("Reservoir constraint must have a min_level <= 0")
1272
1273        ct = Constraint(self)
1274        model_ct = self.__model.constraints[ct.index]
1275        model_ct.reservoir.time_exprs.extend(
1276            [self.parse_linear_expression(x) for x in times]
1277        )
1278        model_ct.reservoir.level_changes.extend(
1279            [self.parse_linear_expression(x) for x in level_changes]
1280        )
1281        model_ct.reservoir.min_level = min_level
1282        model_ct.reservoir.max_level = max_level
1283        return ct

Adds Reservoir(times, level_changes, min_level, max_level).

Maintains a reservoir level within bounds. The water level starts at 0, and at any time, it must be between min_level and max_level.

If the affine expression times[i] is assigned a value t, then the current level changes by level_changes[i], which is constant, at time t.

Note that min level must be <= 0, and the max level must be >= 0. Please use fixed level_changes to simulate initial state.

Therefore, at any time: sum(level_changes[i] if times[i] <= t) in [min_level, max_level]

Arguments:
  • times: A list of 1-var affine expressions (a * x + b) which specify the time of the filling or emptying the reservoir.
  • level_changes: A list of integer values that specifies the amount of the emptying or filling. Currently, variable demands are not supported.
  • min_level: At any time, the level of the reservoir must be greater or equal than the min level.
  • max_level: At any time, the level of the reservoir must be less or equal than the max level.
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: if max_level < min_level.
  • ValueError: if max_level < 0.
  • ValueError: if min_level > 0
def add_reservoir_constraint_with_active( self, times: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], level_changes: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], actives: Iterable[Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]], min_level: int, max_level: int) -> Constraint:
1285    def add_reservoir_constraint_with_active(
1286        self,
1287        times: Iterable[LinearExprT],
1288        level_changes: Iterable[LinearExprT],
1289        actives: Iterable[LiteralT],
1290        min_level: int,
1291        max_level: int,
1292    ) -> Constraint:
1293        """Adds Reservoir(times, level_changes, actives, min_level, max_level).
1294
1295        Maintains a reservoir level within bounds. The water level starts at 0, and
1296        at any time, it must be between min_level and max_level.
1297
1298        If the variable `times[i]` is assigned a value t, and `actives[i]` is
1299        `True`, then the current level changes by `level_changes[i]`, which is
1300        constant,
1301        at time t.
1302
1303         Note that min level must be <= 0, and the max level must be >= 0. Please
1304         use fixed level_changes to simulate initial state.
1305
1306         Therefore, at any time:
1307             sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level,
1308             max_level]
1309
1310
1311        The array of boolean variables 'actives', if defined, indicates which
1312        actions are actually performed.
1313
1314        Args:
1315          times: A list of 1-var affine expressions (a * x + b) which specify the
1316            time of the filling or emptying the reservoir.
1317          level_changes: A list of integer values that specifies the amount of the
1318            emptying or filling. Currently, variable demands are not supported.
1319          actives: a list of boolean variables. They indicates if the
1320            emptying/refilling events actually take place.
1321          min_level: At any time, the level of the reservoir must be greater or
1322            equal than the min level.
1323          max_level: At any time, the level of the reservoir must be less or equal
1324            than the max level.
1325
1326        Returns:
1327          An instance of the `Constraint` class.
1328
1329        Raises:
1330          ValueError: if max_level < min_level.
1331
1332          ValueError: if max_level < 0.
1333
1334          ValueError: if min_level > 0
1335        """
1336
1337        if max_level < min_level:
1338            raise ValueError("Reservoir constraint must have a max_level >= min_level")
1339
1340        if max_level < 0:
1341            raise ValueError("Reservoir constraint must have a max_level >= 0")
1342
1343        if min_level > 0:
1344            raise ValueError("Reservoir constraint must have a min_level <= 0")
1345
1346        ct = Constraint(self)
1347        model_ct = self.__model.constraints[ct.index]
1348        model_ct.reservoir.time_exprs.extend(
1349            [self.parse_linear_expression(x) for x in times]
1350        )
1351        model_ct.reservoir.level_changes.extend(
1352            [self.parse_linear_expression(x) for x in level_changes]
1353        )
1354        model_ct.reservoir.active_literals.extend(
1355            [self.get_or_make_boolean_index(x) for x in actives]
1356        )
1357        model_ct.reservoir.min_level = min_level
1358        model_ct.reservoir.max_level = max_level
1359        return ct

Adds Reservoir(times, level_changes, actives, min_level, max_level).

Maintains a reservoir level within bounds. The water level starts at 0, and at any time, it must be between min_level and max_level.

If the variable times[i] is assigned a value t, and actives[i] is True, then the current level changes by level_changes[i], which is constant, at time t.

Note that min level must be <= 0, and the max level must be >= 0. Please use fixed level_changes to simulate initial state.

Therefore, at any time: sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level, max_level]

The array of boolean variables 'actives', if defined, indicates which actions are actually performed.

Arguments:
  • times: A list of 1-var affine expressions (a * x + b) which specify the time of the filling or emptying the reservoir.
  • level_changes: A list of integer values that specifies the amount of the emptying or filling. Currently, variable demands are not supported.
  • actives: a list of boolean variables. They indicates if the emptying/refilling events actually take place.
  • min_level: At any time, the level of the reservoir must be greater or equal than the min level.
  • max_level: At any time, the level of the reservoir must be less or equal than the max level.
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: if max_level < min_level.
  • ValueError: if max_level < 0.
  • ValueError: if min_level > 0
def add_map_domain( self, var: IntVar, bool_var_array: Iterable[IntVar], offset: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64] = 0):
1361    def add_map_domain(
1362        self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0
1363    ):
1364        """Adds `var == i + offset <=> bool_var_array[i] == true for all i`."""
1365
1366        for i, bool_var in enumerate(bool_var_array):
1367            b_index = bool_var.index
1368            var_index = var.index
1369            model_ct = self.__model.constraints.add()
1370            model_ct.linear.vars.append(var_index)
1371            model_ct.linear.coeffs.append(1)
1372            offset_as_int = int(offset)
1373            model_ct.linear.domain.extend([offset_as_int + i, offset_as_int + i])
1374            model_ct.enforcement_literal.append(b_index)
1375
1376            model_ct = self.__model.constraints.add()
1377            model_ct.linear.vars.append(var_index)
1378            model_ct.linear.coeffs.append(1)
1379            model_ct.enforcement_literal.append(-b_index - 1)
1380            if offset + i - 1 >= INT_MIN:
1381                model_ct.linear.domain.extend([INT_MIN, offset_as_int + i - 1])
1382            if offset + i + 1 <= INT_MAX:
1383                model_ct.linear.domain.extend([offset_as_int + i + 1, INT_MAX])

Adds var == i + offset <=> bool_var_array[i] == true for all i.

def add_implication( self, a: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool], b: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> Constraint:
1385    def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint:
1386        """Adds `a => b` (`a` implies `b`)."""
1387        ct = Constraint(self)
1388        model_ct = self.__model.constraints[ct.index]
1389        model_ct.bool_or.literals.append(self.get_or_make_boolean_index(b))
1390        model_ct.enforcement_literal.append(self.get_or_make_boolean_index(a))
1391        return ct

Adds a => b (a implies b).

def add_bool_or(self, *literals):
1399    def add_bool_or(self, *literals):
1400        """Adds `Or(literals) == true`: sum(literals) >= 1."""
1401        ct = Constraint(self)
1402        model_ct = self.__model.constraints[ct.index]
1403        model_ct.bool_or.literals.extend(
1404            [
1405                self.get_or_make_boolean_index(x)
1406                for x in expand_generator_or_tuple(literals)
1407            ]
1408        )
1409        return ct

Adds Or(literals) == true: sum(literals) >= 1.

def add_at_least_one(self, *literals):
1417    def add_at_least_one(self, *literals):
1418        """Same as `add_bool_or`: `sum(literals) >= 1`."""
1419        return self.add_bool_or(*literals)

Same as add_bool_or: sum(literals) >= 1.

def add_at_most_one(self, *literals):
1427    def add_at_most_one(self, *literals):
1428        """Adds `AtMostOne(literals)`: `sum(literals) <= 1`."""
1429        ct = Constraint(self)
1430        model_ct = self.__model.constraints[ct.index]
1431        model_ct.at_most_one.literals.extend(
1432            [
1433                self.get_or_make_boolean_index(x)
1434                for x in expand_generator_or_tuple(literals)
1435            ]
1436        )
1437        return ct

Adds AtMostOne(literals): sum(literals) <= 1.

def add_exactly_one(self, *literals):
1445    def add_exactly_one(self, *literals):
1446        """Adds `ExactlyOne(literals)`: `sum(literals) == 1`."""
1447        ct = Constraint(self)
1448        model_ct = self.__model.constraints[ct.index]
1449        model_ct.exactly_one.literals.extend(
1450            [
1451                self.get_or_make_boolean_index(x)
1452                for x in expand_generator_or_tuple(literals)
1453            ]
1454        )
1455        return ct

Adds ExactlyOne(literals): sum(literals) == 1.

def add_bool_and(self, *literals):
1463    def add_bool_and(self, *literals):
1464        """Adds `And(literals) == true`."""
1465        ct = Constraint(self)
1466        model_ct = self.__model.constraints[ct.index]
1467        model_ct.bool_and.literals.extend(
1468            [
1469                self.get_or_make_boolean_index(x)
1470                for x in expand_generator_or_tuple(literals)
1471            ]
1472        )
1473        return ct

Adds And(literals) == true.

def add_bool_xor(self, *literals):
1481    def add_bool_xor(self, *literals):
1482        """Adds `XOr(literals) == true`.
1483
1484        In contrast to add_bool_or and add_bool_and, it does not support
1485            .only_enforce_if().
1486
1487        Args:
1488          *literals: the list of literals in the constraint.
1489
1490        Returns:
1491          An `Constraint` object.
1492        """
1493        ct = Constraint(self)
1494        model_ct = self.__model.constraints[ct.index]
1495        model_ct.bool_xor.literals.extend(
1496            [
1497                self.get_or_make_boolean_index(x)
1498                for x in expand_generator_or_tuple(literals)
1499            ]
1500        )
1501        return ct

Adds XOr(literals) == true.

In contrast to add_bool_or and add_bool_and, it does not support .only_enforce_if().

Arguments:
  • *literals: the list of literals in the constraint.
Returns:

An Constraint object.

def add_min_equality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], exprs: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]) -> Constraint:
1503    def add_min_equality(
1504        self, target: LinearExprT, exprs: Iterable[LinearExprT]
1505    ) -> Constraint:
1506        """Adds `target == Min(exprs)`."""
1507        ct = Constraint(self)
1508        model_ct = self.__model.constraints[ct.index]
1509        model_ct.lin_max.exprs.extend(
1510            [self.parse_linear_expression(x, True) for x in exprs]
1511        )
1512        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target, True))
1513        return ct

Adds target == Min(exprs).

def add_max_equality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], exprs: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]) -> Constraint:
1515    def add_max_equality(
1516        self, target: LinearExprT, exprs: Iterable[LinearExprT]
1517    ) -> Constraint:
1518        """Adds `target == Max(exprs)`."""
1519        ct = Constraint(self)
1520        model_ct = self.__model.constraints[ct.index]
1521        model_ct.lin_max.exprs.extend([self.parse_linear_expression(x) for x in exprs])
1522        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
1523        return ct

Adds target == Max(exprs).

def add_division_equality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], num: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], denom: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
1525    def add_division_equality(
1526        self, target: LinearExprT, num: LinearExprT, denom: LinearExprT
1527    ) -> Constraint:
1528        """Adds `target == num // denom` (integer division rounded towards 0)."""
1529        ct = Constraint(self)
1530        model_ct = self.__model.constraints[ct.index]
1531        model_ct.int_div.exprs.append(self.parse_linear_expression(num))
1532        model_ct.int_div.exprs.append(self.parse_linear_expression(denom))
1533        model_ct.int_div.target.CopyFrom(self.parse_linear_expression(target))
1534        return ct

Adds target == num // denom (integer division rounded towards 0).

def add_abs_equality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
1536    def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint:
1537        """Adds `target == Abs(expr)`."""
1538        ct = Constraint(self)
1539        model_ct = self.__model.constraints[ct.index]
1540        model_ct.lin_max.exprs.append(self.parse_linear_expression(expr))
1541        model_ct.lin_max.exprs.append(self.parse_linear_expression(expr, True))
1542        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
1543        return ct

Adds target == Abs(expr).

def add_modulo_equality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], mod: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
1545    def add_modulo_equality(
1546        self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT
1547    ) -> Constraint:
1548        """Adds `target = expr % mod`.
1549
1550        It uses the C convention, that is the result is the remainder of the
1551        integral division rounded towards 0.
1552
1553            For example:
1554            * 10 % 3 = 1
1555            * -10 % 3 = -1
1556            * 10 % -3 = 1
1557            * -10 % -3 = -1
1558
1559        Args:
1560          target: the target expression.
1561          expr: the expression to compute the modulo of.
1562          mod: the modulus expression.
1563
1564        Returns:
1565          A `Constraint` object.
1566        """
1567        ct = Constraint(self)
1568        model_ct = self.__model.constraints[ct.index]
1569        model_ct.int_mod.exprs.append(self.parse_linear_expression(expr))
1570        model_ct.int_mod.exprs.append(self.parse_linear_expression(mod))
1571        model_ct.int_mod.target.CopyFrom(self.parse_linear_expression(target))
1572        return ct

Adds target = expr % mod.

It uses the C convention, that is the result is the remainder of the integral division rounded towards 0.

For example:
* 10 % 3 = 1
* -10 % 3 = -1
* 10 % -3 = 1
* -10 % -3 = -1
Arguments:
  • target: the target expression.
  • expr: the expression to compute the modulo of.
  • mod: the modulus expression.
Returns:

A Constraint object.

def add_multiplication_equality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], *expressions: Union[Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
1574    def add_multiplication_equality(
1575        self,
1576        target: LinearExprT,
1577        *expressions: Union[Iterable[LinearExprT], LinearExprT],
1578    ) -> Constraint:
1579        """Adds `target == expressions[0] * .. * expressions[n]`."""
1580        ct = Constraint(self)
1581        model_ct = self.__model.constraints[ct.index]
1582        model_ct.int_prod.exprs.extend(
1583            [
1584                self.parse_linear_expression(expr)
1585                for expr in expand_generator_or_tuple(expressions)
1586            ]
1587        )
1588        model_ct.int_prod.target.CopyFrom(self.parse_linear_expression(target))
1589        return ct

Adds target == expressions[0] * .. * expressions[n].

def new_interval_var( self, start: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], size: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], end: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], name: str) -> IntervalVar:
1593    def new_interval_var(
1594        self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str
1595    ) -> IntervalVar:
1596        """Creates an interval variable from start, size, and end.
1597
1598        An interval variable is a constraint, that is itself used in other
1599        constraints like NoOverlap.
1600
1601        Internally, it ensures that `start + size == end`.
1602
1603        Args:
1604          start: The start of the interval. It must be of the form a * var + b.
1605          size: The size of the interval. It must be of the form a * var + b.
1606          end: The end of the interval. It must be of the form a * var + b.
1607          name: The name of the interval variable.
1608
1609        Returns:
1610          An `IntervalVar` object.
1611        """
1612
1613        start_expr = self.parse_linear_expression(start)
1614        size_expr = self.parse_linear_expression(size)
1615        end_expr = self.parse_linear_expression(end)
1616        if len(start_expr.vars) > 1:
1617            raise TypeError(
1618                "cp_model.new_interval_var: start must be 1-var affine or constant."
1619            )
1620        if len(size_expr.vars) > 1:
1621            raise TypeError(
1622                "cp_model.new_interval_var: size must be 1-var affine or constant."
1623            )
1624        if len(end_expr.vars) > 1:
1625            raise TypeError(
1626                "cp_model.new_interval_var: end must be 1-var affine or constant."
1627            )
1628        return IntervalVar(
1629            self.__model,
1630            self.__var_list,
1631            start_expr,
1632            size_expr,
1633            end_expr,
1634            None,
1635            name,
1636        )

Creates an interval variable from start, size, and end.

An interval variable is a constraint, that is itself used in other constraints like NoOverlap.

Internally, it ensures that start + size == end.

Arguments:
  • start: The start of the interval. It must be of the form a * var + b.
  • size: The size of the interval. It must be of the form a * var + b.
  • end: The end of the interval. It must be of the form a * var + b.
  • name: The name of the interval variable.
Returns:

An IntervalVar object.

def new_interval_var_series( self, name: str, index: pandas.core.indexes.base.Index, starts: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], sizes: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], ends: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series]) -> pandas.core.series.Series:
1638    def new_interval_var_series(
1639        self,
1640        name: str,
1641        index: pd.Index,
1642        starts: Union[LinearExprT, pd.Series],
1643        sizes: Union[LinearExprT, pd.Series],
1644        ends: Union[LinearExprT, pd.Series],
1645    ) -> pd.Series:
1646        """Creates a series of interval variables with the given name.
1647
1648        Args:
1649          name (str): Required. The name of the variable set.
1650          index (pd.Index): Required. The index to use for the variable set.
1651          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1652            set. If a `pd.Series` is passed in, it will be based on the
1653            corresponding values of the pd.Series.
1654          sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1655            set. If a `pd.Series` is passed in, it will be based on the
1656            corresponding values of the pd.Series.
1657          ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1658            set. If a `pd.Series` is passed in, it will be based on the
1659            corresponding values of the pd.Series.
1660
1661        Returns:
1662          pd.Series: The interval variable set indexed by its corresponding
1663          dimensions.
1664
1665        Raises:
1666          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1667          ValueError: if the `name` is not a valid identifier or already exists.
1668          ValueError: if the all the indexes do not match.
1669        """
1670        if not isinstance(index, pd.Index):
1671            raise TypeError("Non-index object is used as index")
1672        if not name.isidentifier():
1673            raise ValueError(f"name={name!r} is not a valid identifier")
1674
1675        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1676        sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
1677        ends = _convert_to_linear_expr_series_and_validate_index(ends, index)
1678        interval_array = []
1679        for i in index:
1680            interval_array.append(
1681                self.new_interval_var(
1682                    start=starts[i],
1683                    size=sizes[i],
1684                    end=ends[i],
1685                    name=f"{name}[{i}]",
1686                )
1687            )
1688        return pd.Series(index=index, data=interval_array)

Creates a series of interval variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • starts (Union[LinearExprT, pd.Series]): The start of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • sizes (Union[LinearExprT, pd.Series]): The size of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • ends (Union[LinearExprT, pd.Series]): The ends of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The interval variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the all the indexes do not match.
def new_fixed_size_interval_var( self, start: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], size: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], name: str) -> IntervalVar:
1690    def new_fixed_size_interval_var(
1691        self, start: LinearExprT, size: IntegralT, name: str
1692    ) -> IntervalVar:
1693        """Creates an interval variable from start, and a fixed size.
1694
1695        An interval variable is a constraint, that is itself used in other
1696        constraints like NoOverlap.
1697
1698        Args:
1699          start: The start of the interval. It must be of the form a * var + b.
1700          size: The size of the interval. It must be an integer value.
1701          name: The name of the interval variable.
1702
1703        Returns:
1704          An `IntervalVar` object.
1705        """
1706        start_expr = self.parse_linear_expression(start)
1707        size_expr = self.parse_linear_expression(size)
1708        end_expr = self.parse_linear_expression(start + size)
1709        if len(start_expr.vars) > 1:
1710            raise TypeError(
1711                "cp_model.new_interval_var: start must be affine or constant."
1712            )
1713        return IntervalVar(
1714            self.__model,
1715            self.__var_list,
1716            start_expr,
1717            size_expr,
1718            end_expr,
1719            None,
1720            name,
1721        )

Creates an interval variable from start, and a fixed size.

An interval variable is a constraint, that is itself used in other constraints like NoOverlap.

Arguments:
  • start: The start of the interval. It must be of the form a * var + b.
  • size: The size of the interval. It must be an integer value.
  • name: The name of the interval variable.
Returns:

An IntervalVar object.

def new_fixed_size_interval_var_series( self, name: str, index: pandas.core.indexes.base.Index, starts: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], sizes: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series]) -> pandas.core.series.Series:
1723    def new_fixed_size_interval_var_series(
1724        self,
1725        name: str,
1726        index: pd.Index,
1727        starts: Union[LinearExprT, pd.Series],
1728        sizes: Union[IntegralT, pd.Series],
1729    ) -> pd.Series:
1730        """Creates a series of interval variables with the given name.
1731
1732        Args:
1733          name (str): Required. The name of the variable set.
1734          index (pd.Index): Required. The index to use for the variable set.
1735          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1736            set. If a `pd.Series` is passed in, it will be based on the
1737            corresponding values of the pd.Series.
1738          sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1739            the set. If a `pd.Series` is passed in, it will be based on the
1740            corresponding values of the pd.Series.
1741
1742        Returns:
1743          pd.Series: The interval variable set indexed by its corresponding
1744          dimensions.
1745
1746        Raises:
1747          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1748          ValueError: if the `name` is not a valid identifier or already exists.
1749          ValueError: if the all the indexes do not match.
1750        """
1751        if not isinstance(index, pd.Index):
1752            raise TypeError("Non-index object is used as index")
1753        if not name.isidentifier():
1754            raise ValueError(f"name={name!r} is not a valid identifier")
1755
1756        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1757        sizes = _convert_to_integral_series_and_validate_index(sizes, index)
1758        interval_array = []
1759        for i in index:
1760            interval_array.append(
1761                self.new_fixed_size_interval_var(
1762                    start=starts[i],
1763                    size=sizes[i],
1764                    name=f"{name}[{i}]",
1765                )
1766            )
1767        return pd.Series(index=index, data=interval_array)

Creates a series of interval variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • starts (Union[LinearExprT, pd.Series]): The start of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The interval variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the all the indexes do not match.
def new_optional_interval_var( self, start: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], size: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], end: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], is_present: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool], name: str) -> IntervalVar:
1769    def new_optional_interval_var(
1770        self,
1771        start: LinearExprT,
1772        size: LinearExprT,
1773        end: LinearExprT,
1774        is_present: LiteralT,
1775        name: str,
1776    ) -> IntervalVar:
1777        """Creates an optional interval var from start, size, end, and is_present.
1778
1779        An optional interval variable is a constraint, that is itself used in other
1780        constraints like NoOverlap. This constraint is protected by a presence
1781        literal that indicates if it is active or not.
1782
1783        Internally, it ensures that `is_present` implies `start + size ==
1784        end`.
1785
1786        Args:
1787          start: The start of the interval. It must be of the form a * var + b.
1788          size: The size of the interval. It must be of the form a * var + b.
1789          end: The end of the interval. It must be of the form a * var + b.
1790          is_present: A literal that indicates if the interval is active or not. A
1791            inactive interval is simply ignored by all constraints.
1792          name: The name of the interval variable.
1793
1794        Returns:
1795          An `IntervalVar` object.
1796        """
1797
1798        # Creates the IntervalConstraintProto object.
1799        is_present_index = self.get_or_make_boolean_index(is_present)
1800        start_expr = self.parse_linear_expression(start)
1801        size_expr = self.parse_linear_expression(size)
1802        end_expr = self.parse_linear_expression(end)
1803        if len(start_expr.vars) > 1:
1804            raise TypeError(
1805                "cp_model.new_interval_var: start must be affine or constant."
1806            )
1807        if len(size_expr.vars) > 1:
1808            raise TypeError(
1809                "cp_model.new_interval_var: size must be affine or constant."
1810            )
1811        if len(end_expr.vars) > 1:
1812            raise TypeError(
1813                "cp_model.new_interval_var: end must be affine or constant."
1814            )
1815        return IntervalVar(
1816            self.__model,
1817            self.__var_list,
1818            start_expr,
1819            size_expr,
1820            end_expr,
1821            is_present_index,
1822            name,
1823        )

Creates an optional interval var from start, size, end, and is_present.

An optional interval variable is a constraint, that is itself used in other constraints like NoOverlap. This constraint is protected by a presence literal that indicates if it is active or not.

Internally, it ensures that is_present implies start + size == end.

Arguments:
  • start: The start of the interval. It must be of the form a * var + b.
  • size: The size of the interval. It must be of the form a * var + b.
  • end: The end of the interval. It must be of the form a * var + b.
  • is_present: A literal that indicates if the interval is active or not. A inactive interval is simply ignored by all constraints.
  • name: The name of the interval variable.
Returns:

An IntervalVar object.

def new_optional_interval_var_series( self, name: str, index: pandas.core.indexes.base.Index, starts: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], sizes: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], ends: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], are_present: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool, pandas.core.series.Series]) -> pandas.core.series.Series:
1825    def new_optional_interval_var_series(
1826        self,
1827        name: str,
1828        index: pd.Index,
1829        starts: Union[LinearExprT, pd.Series],
1830        sizes: Union[LinearExprT, pd.Series],
1831        ends: Union[LinearExprT, pd.Series],
1832        are_present: Union[LiteralT, pd.Series],
1833    ) -> pd.Series:
1834        """Creates a series of interval variables with the given name.
1835
1836        Args:
1837          name (str): Required. The name of the variable set.
1838          index (pd.Index): Required. The index to use for the variable set.
1839          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1840            set. If a `pd.Series` is passed in, it will be based on the
1841            corresponding values of the pd.Series.
1842          sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1843            set. If a `pd.Series` is passed in, it will be based on the
1844            corresponding values of the pd.Series.
1845          ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1846            set. If a `pd.Series` is passed in, it will be based on the
1847            corresponding values of the pd.Series.
1848          are_present (Union[LiteralT, pd.Series]): The performed literal of each
1849            interval in the set. If a `pd.Series` is passed in, it will be based on
1850            the corresponding values of the pd.Series.
1851
1852        Returns:
1853          pd.Series: The interval variable set indexed by its corresponding
1854          dimensions.
1855
1856        Raises:
1857          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1858          ValueError: if the `name` is not a valid identifier or already exists.
1859          ValueError: if the all the indexes do not match.
1860        """
1861        if not isinstance(index, pd.Index):
1862            raise TypeError("Non-index object is used as index")
1863        if not name.isidentifier():
1864            raise ValueError(f"name={name!r} is not a valid identifier")
1865
1866        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1867        sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
1868        ends = _convert_to_linear_expr_series_and_validate_index(ends, index)
1869        are_present = _convert_to_literal_series_and_validate_index(are_present, index)
1870
1871        interval_array = []
1872        for i in index:
1873            interval_array.append(
1874                self.new_optional_interval_var(
1875                    start=starts[i],
1876                    size=sizes[i],
1877                    end=ends[i],
1878                    is_present=are_present[i],
1879                    name=f"{name}[{i}]",
1880                )
1881            )
1882        return pd.Series(index=index, data=interval_array)

Creates a series of interval variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • starts (Union[LinearExprT, pd.Series]): The start of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • sizes (Union[LinearExprT, pd.Series]): The size of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • ends (Union[LinearExprT, pd.Series]): The ends of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • are_present (Union[LiteralT, pd.Series]): The performed literal of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The interval variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the all the indexes do not match.
def new_optional_fixed_size_interval_var( self, start: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], size: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], is_present: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool], name: str) -> IntervalVar:
1884    def new_optional_fixed_size_interval_var(
1885        self,
1886        start: LinearExprT,
1887        size: IntegralT,
1888        is_present: LiteralT,
1889        name: str,
1890    ) -> IntervalVar:
1891        """Creates an interval variable from start, and a fixed size.
1892
1893        An interval variable is a constraint, that is itself used in other
1894        constraints like NoOverlap.
1895
1896        Args:
1897          start: The start of the interval. It must be of the form a * var + b.
1898          size: The size of the interval. It must be an integer value.
1899          is_present: A literal that indicates if the interval is active or not. A
1900            inactive interval is simply ignored by all constraints.
1901          name: The name of the interval variable.
1902
1903        Returns:
1904          An `IntervalVar` object.
1905        """
1906        start_expr = self.parse_linear_expression(start)
1907        size_expr = self.parse_linear_expression(size)
1908        end_expr = self.parse_linear_expression(start + size)
1909        if len(start_expr.vars) > 1:
1910            raise TypeError(
1911                "cp_model.new_interval_var: start must be affine or constant."
1912            )
1913        is_present_index = self.get_or_make_boolean_index(is_present)
1914        return IntervalVar(
1915            self.__model,
1916            self.__var_list,
1917            start_expr,
1918            size_expr,
1919            end_expr,
1920            is_present_index,
1921            name,
1922        )

Creates an interval variable from start, and a fixed size.

An interval variable is a constraint, that is itself used in other constraints like NoOverlap.

Arguments:
  • start: The start of the interval. It must be of the form a * var + b.
  • size: The size of the interval. It must be an integer value.
  • is_present: A literal that indicates if the interval is active or not. A inactive interval is simply ignored by all constraints.
  • name: The name of the interval variable.
Returns:

An IntervalVar object.

def new_optional_fixed_size_interval_var_series( self, name: str, index: pandas.core.indexes.base.Index, starts: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], sizes: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], are_present: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool, pandas.core.series.Series]) -> pandas.core.series.Series:
1924    def new_optional_fixed_size_interval_var_series(
1925        self,
1926        name: str,
1927        index: pd.Index,
1928        starts: Union[LinearExprT, pd.Series],
1929        sizes: Union[IntegralT, pd.Series],
1930        are_present: Union[LiteralT, pd.Series],
1931    ) -> pd.Series:
1932        """Creates a series of interval variables with the given name.
1933
1934        Args:
1935          name (str): Required. The name of the variable set.
1936          index (pd.Index): Required. The index to use for the variable set.
1937          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1938            set. If a `pd.Series` is passed in, it will be based on the
1939            corresponding values of the pd.Series.
1940          sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1941            the set. If a `pd.Series` is passed in, it will be based on the
1942            corresponding values of the pd.Series.
1943          are_present (Union[LiteralT, pd.Series]): The performed literal of each
1944            interval in the set. If a `pd.Series` is passed in, it will be based on
1945            the corresponding values of the pd.Series.
1946
1947        Returns:
1948          pd.Series: The interval variable set indexed by its corresponding
1949          dimensions.
1950
1951        Raises:
1952          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1953          ValueError: if the `name` is not a valid identifier or already exists.
1954          ValueError: if the all the indexes do not match.
1955        """
1956        if not isinstance(index, pd.Index):
1957            raise TypeError("Non-index object is used as index")
1958        if not name.isidentifier():
1959            raise ValueError(f"name={name!r} is not a valid identifier")
1960
1961        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1962        sizes = _convert_to_integral_series_and_validate_index(sizes, index)
1963        are_present = _convert_to_literal_series_and_validate_index(are_present, index)
1964        interval_array = []
1965        for i in index:
1966            interval_array.append(
1967                self.new_optional_fixed_size_interval_var(
1968                    start=starts[i],
1969                    size=sizes[i],
1970                    is_present=are_present[i],
1971                    name=f"{name}[{i}]",
1972                )
1973            )
1974        return pd.Series(index=index, data=interval_array)

Creates a series of interval variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • starts (Union[LinearExprT, pd.Series]): The start of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • are_present (Union[LiteralT, pd.Series]): The performed literal of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The interval variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the all the indexes do not match.
def add_no_overlap( self, interval_vars: Iterable[IntervalVar]) -> Constraint:
1976    def add_no_overlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint:
1977        """Adds NoOverlap(interval_vars).
1978
1979        A NoOverlap constraint ensures that all present intervals do not overlap
1980        in time.
1981
1982        Args:
1983          interval_vars: The list of interval variables to constrain.
1984
1985        Returns:
1986          An instance of the `Constraint` class.
1987        """
1988        ct = Constraint(self)
1989        model_ct = self.__model.constraints[ct.index]
1990        model_ct.no_overlap.intervals.extend(
1991            [self.get_interval_index(x) for x in interval_vars]
1992        )
1993        return ct

Adds NoOverlap(interval_vars).

A NoOverlap constraint ensures that all present intervals do not overlap in time.

Arguments:
  • interval_vars: The list of interval variables to constrain.
Returns:

An instance of the Constraint class.

def add_no_overlap_2d( self, x_intervals: Iterable[IntervalVar], y_intervals: Iterable[IntervalVar]) -> Constraint:
1995    def add_no_overlap_2d(
1996        self,
1997        x_intervals: Iterable[IntervalVar],
1998        y_intervals: Iterable[IntervalVar],
1999    ) -> Constraint:
2000        """Adds NoOverlap2D(x_intervals, y_intervals).
2001
2002        A NoOverlap2D constraint ensures that all present rectangles do not overlap
2003        on a plane. Each rectangle is aligned with the X and Y axis, and is defined
2004        by two intervals which represent its projection onto the X and Y axis.
2005
2006        Furthermore, one box is optional if at least one of the x or y interval is
2007        optional.
2008
2009        Args:
2010          x_intervals: The X coordinates of the rectangles.
2011          y_intervals: The Y coordinates of the rectangles.
2012
2013        Returns:
2014          An instance of the `Constraint` class.
2015        """
2016        ct = Constraint(self)
2017        model_ct = self.__model.constraints[ct.index]
2018        model_ct.no_overlap_2d.x_intervals.extend(
2019            [self.get_interval_index(x) for x in x_intervals]
2020        )
2021        model_ct.no_overlap_2d.y_intervals.extend(
2022            [self.get_interval_index(x) for x in y_intervals]
2023        )
2024        return ct

Adds NoOverlap2D(x_intervals, y_intervals).

A NoOverlap2D constraint ensures that all present rectangles do not overlap on a plane. Each rectangle is aligned with the X and Y axis, and is defined by two intervals which represent its projection onto the X and Y axis.

Furthermore, one box is optional if at least one of the x or y interval is optional.

Arguments:
  • x_intervals: The X coordinates of the rectangles.
  • y_intervals: The Y coordinates of the rectangles.
Returns:

An instance of the Constraint class.

def add_cumulative( self, intervals: Iterable[IntervalVar], demands: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], capacity: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
2026    def add_cumulative(
2027        self,
2028        intervals: Iterable[IntervalVar],
2029        demands: Iterable[LinearExprT],
2030        capacity: LinearExprT,
2031    ) -> Constraint:
2032        """Adds Cumulative(intervals, demands, capacity).
2033
2034        This constraint enforces that:
2035
2036            for all t:
2037              sum(demands[i]
2038                if (start(intervals[i]) <= t < end(intervals[i])) and
2039                (intervals[i] is present)) <= capacity
2040
2041        Args:
2042          intervals: The list of intervals.
2043          demands: The list of demands for each interval. Each demand must be >= 0.
2044            Each demand can be a 1-var affine expression (a * x + b).
2045          capacity: The maximum capacity of the cumulative constraint. It can be a
2046            1-var affine expression (a * x + b).
2047
2048        Returns:
2049          An instance of the `Constraint` class.
2050        """
2051        cumulative = Constraint(self)
2052        model_ct = self.__model.constraints[cumulative.index]
2053        model_ct.cumulative.intervals.extend(
2054            [self.get_interval_index(x) for x in intervals]
2055        )
2056        for d in demands:
2057            model_ct.cumulative.demands.append(self.parse_linear_expression(d))
2058        model_ct.cumulative.capacity.CopyFrom(self.parse_linear_expression(capacity))
2059        return cumulative

Adds Cumulative(intervals, demands, capacity).

This constraint enforces that:

for all t: sum(demands[i] if (start(intervals[i]) <= t < end(intervals[i])) and (intervals[i] is present)) <= capacity

Arguments:
  • intervals: The list of intervals.
  • demands: The list of demands for each interval. Each demand must be >= 0. Each demand can be a 1-var affine expression (a * x + b).
  • capacity: The maximum capacity of the cumulative constraint. It can be a 1-var affine expression (a * x + b).
Returns:

An instance of the Constraint class.

def clone(self) -> CpModel:
2062    def clone(self) -> "CpModel":
2063        """Reset the model, and creates a new one from a CpModelProto instance."""
2064        clone = CpModel()
2065        clone.proto.CopyFrom(self.proto)
2066        clone.rebuild_var_and_constant_map()
2067        return clone

Reset the model, and creates a new one from a CpModelProto instance.

def rebuild_var_and_constant_map(self):
2075    def rebuild_var_and_constant_map(self):
2076        """Internal method used during model cloning."""
2077        for i, var in enumerate(self.__model.variables):
2078            if len(var.domain) == 2 and var.domain[0] == var.domain[1]:
2079                self.__constant_map[var.domain[0]] = i
2080            is_boolean = (
2081                len(var.domain) == 2 and var.domain[0] >= 0 and var.domain[1] <= 1
2082            )
2083            self.__var_list.append(IntVar(self.__model, i, is_boolean, None))

Internal method used during model cloning.

def get_bool_var_from_proto_index(self, index: int) -> IntVar:
2085    def get_bool_var_from_proto_index(self, index: int) -> IntVar:
2086        """Returns an already created Boolean variable from its index."""
2087        result = self._get_int_var(index)
2088        if not result.is_boolean:
2089            raise ValueError(
2090                f"get_bool_var_from_proto_index: index {index} does not reference a"
2091                " boolean variable"
2092            )
2093        return result

Returns an already created Boolean variable from its index.

def get_int_var_from_proto_index(self, index: int) -> IntVar:
2095    def get_int_var_from_proto_index(self, index: int) -> IntVar:
2096        """Returns an already created integer variable from its index."""
2097        return self._get_int_var(index)

Returns an already created integer variable from its index.

def get_interval_var_from_proto_index(self, index: int) -> IntervalVar:
2099    def get_interval_var_from_proto_index(self, index: int) -> IntervalVar:
2100        """Returns an already created interval variable from its index."""
2101        if index < 0 or index >= len(self.__model.constraints):
2102            raise ValueError(
2103                f"get_interval_var_from_proto_index: out of bound index {index}"
2104            )
2105        ct = self.__model.constraints[index]
2106        if not ct.HasField("interval"):
2107            raise ValueError(
2108                f"get_interval_var_from_proto_index: index {index} does not"
2109                " reference an" + " interval variable"
2110            )
2111
2112        return IntervalVar(self.__model, self.__var_list, index, None, None, None, None)

Returns an already created interval variable from its index.

2119    @property
2120    def proto(self) -> cp_model_pb2.CpModelProto:
2121        """Returns the underlying CpModelProto."""
2122        return self.__model

Returns the underlying CpModelProto.

def negated(self, index: int) -> int:
2124    def negated(self, index: int) -> int:
2125        return -index - 1
def get_or_make_index( self, arg: Union[IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> int:
2127    def get_or_make_index(self, arg: VariableT) -> int:
2128        """Returns the index of a variable, its negation, or a number."""
2129        if isinstance(arg, IntVar):
2130            return arg.index
2131        if isinstance(arg, IntegralTypes):
2132            return self.get_or_make_index_from_constant(arg)
2133        raise TypeError(
2134            f"NotSupported: model.get_or_make_index({type(arg).__name__!r})"
2135        )

Returns the index of a variable, its negation, or a number.

def get_or_make_boolean_index( self, arg: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> int:
2137    def get_or_make_boolean_index(self, arg: LiteralT) -> int:
2138        """Returns an index from a boolean expression."""
2139        if isinstance(arg, IntVar):
2140            self.assert_is_boolean_variable(arg)
2141            return arg.index
2142        if isinstance(arg, cmh.NotBooleanVariable):
2143            self.assert_is_boolean_variable(arg.negated())
2144            return arg.index
2145        if isinstance(arg, IntegralTypes):
2146            if arg == ~False:  # -1
2147                return self.get_or_make_index_from_constant(1)
2148            if arg == ~True:  # -2
2149                return self.get_or_make_index_from_constant(0)
2150            arg = cmn.assert_is_zero_or_one(arg)
2151            return self.get_or_make_index_from_constant(arg)
2152        if cmn.is_boolean(arg):
2153            return self.get_or_make_index_from_constant(int(arg))
2154        raise TypeError(
2155            "not supported:" f" model.get_or_make_boolean_index({type(arg).__name__!r})"
2156        )

Returns an index from a boolean expression.

def get_interval_index(self, arg: IntervalVar) -> int:
2158    def get_interval_index(self, arg: IntervalVar) -> int:
2159        if not isinstance(arg, IntervalVar):
2160            raise TypeError(
2161                f"NotSupported: model.get_interval_index({type(arg).__name__!r})"
2162            )
2163        return arg.index
def get_or_make_index_from_constant( self, value: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> int:
2165    def get_or_make_index_from_constant(self, value: IntegralT) -> int:
2166        if value in self.__constant_map:
2167            return self.__constant_map[value]
2168        constant_var = self.new_int_var(value, value, "")
2169        self.__constant_map[value] = constant_var.index
2170        return constant_var.index
def parse_linear_expression( self, linear_expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], negate: bool = False) -> ortools.sat.cp_model_pb2.LinearExpressionProto:
2172    def parse_linear_expression(
2173        self, linear_expr: LinearExprT, negate: bool = False
2174    ) -> cp_model_pb2.LinearExpressionProto:
2175        """Returns a LinearExpressionProto built from a LinearExpr instance."""
2176        result: cp_model_pb2.LinearExpressionProto = (
2177            cp_model_pb2.LinearExpressionProto()
2178        )
2179        mult = -1 if negate else 1
2180        if isinstance(linear_expr, IntegralTypes):
2181            result.offset = int(linear_expr) * mult
2182            return result
2183
2184        # Raises TypeError if linear_expr is not an integer.
2185        flat_expr = cmh.FlatIntExpr(linear_expr)
2186        result.offset = flat_expr.offset
2187        for var in flat_expr.vars:
2188            result.vars.append(var.index)
2189        for coeff in flat_expr.coeffs:
2190            result.coeffs.append(coeff * mult)
2191        return result

Returns a LinearExpressionProto built from a LinearExpr instance.

def minimize( self, obj: Union[LinearExpr, int, float, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, numpy.float64]):
2225    def minimize(self, obj: ObjLinearExprT):
2226        """Sets the objective of the model to minimize(obj)."""
2227        self._set_objective(obj, minimize=True)

Sets the objective of the model to minimize(obj).

def maximize( self, obj: Union[LinearExpr, int, float, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, numpy.float64]):
2229    def maximize(self, obj: ObjLinearExprT):
2230        """Sets the objective of the model to maximize(obj)."""
2231        self._set_objective(obj, minimize=False)

Sets the objective of the model to maximize(obj).

def has_objective(self) -> bool:
2233    def has_objective(self) -> bool:
2234        return self.__model.HasField("objective") or self.__model.HasField(
2235            "floating_point_objective"
2236        )
def clear_objective(self):
2238    def clear_objective(self):
2239        self.__model.ClearField("objective")
2240        self.__model.ClearField("floating_point_objective")
def add_decision_strategy( self, variables: Sequence[IntVar], var_strategy: <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object>, domain_strategy: <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object>) -> None:
2242    def add_decision_strategy(
2243        self,
2244        variables: Sequence[IntVar],
2245        var_strategy: cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy,
2246        domain_strategy: cp_model_pb2.DecisionStrategyProto.DomainReductionStrategy,
2247    ) -> None:
2248        """Adds a search strategy to the model.
2249
2250        Args:
2251          variables: a list of variables this strategy will assign.
2252          var_strategy: heuristic to choose the next variable to assign.
2253          domain_strategy: heuristic to reduce the domain of the selected variable.
2254            Currently, this is advanced code: the union of all strategies added to
2255            the model must be complete, i.e. instantiates all variables. Otherwise,
2256            solve() will fail.
2257        """
2258
2259        strategy: cp_model_pb2.DecisionStrategyProto = (
2260            self.__model.search_strategy.add()
2261        )
2262        for v in variables:
2263            expr = strategy.exprs.add()
2264            if v.index >= 0:
2265                expr.vars.append(v.index)
2266                expr.coeffs.append(1)
2267            else:
2268                expr.vars.append(self.negated(v.index))
2269                expr.coeffs.append(-1)
2270                expr.offset = 1
2271
2272        strategy.variable_selection_strategy = var_strategy
2273        strategy.domain_reduction_strategy = domain_strategy

Adds a search strategy to the model.

Arguments:
  • variables: a list of variables this strategy will assign.
  • var_strategy: heuristic to choose the next variable to assign.
  • domain_strategy: heuristic to reduce the domain of the selected variable. Currently, this is advanced code: the union of all strategies added to the model must be complete, i.e. instantiates all variables. Otherwise, solve() will fail.
def model_stats(self) -> str:
2275    def model_stats(self) -> str:
2276        """Returns a string containing some model statistics."""
2277        return cmh.CpSatHelper.model_stats(self.__model)

Returns a string containing some model statistics.

def validate(self) -> str:
2279    def validate(self) -> str:
2280        """Returns a string indicating that the model is invalid."""
2281        return cmh.CpSatHelper.validate_model(self.__model)

Returns a string indicating that the model is invalid.

def export_to_file(self, file: str) -> bool:
2283    def export_to_file(self, file: str) -> bool:
2284        """Write the model as a protocol buffer to 'file'.
2285
2286        Args:
2287          file: file to write the model to. If the filename ends with 'txt', the
2288            model will be written as a text file, otherwise, the binary format will
2289            be used.
2290
2291        Returns:
2292          True if the model was correctly written.
2293        """
2294        return cmh.CpSatHelper.write_model_to_file(self.__model, file)

Write the model as a protocol buffer to 'file'.

Arguments:
  • file: file to write the model to. If the filename ends with 'txt', the model will be written as a text file, otherwise, the binary format will be used.
Returns:

True if the model was correctly written.

def add_hint(self, var, value) -> None:
2302    def add_hint(self, var, value) -> None:
2303        """Adds 'var == value' as a hint to the solver."""
2304        if var.index >= 0:
2305            self.__model.solution_hint.vars.append(self.get_or_make_index(var))
2306            self.__model.solution_hint.values.append(int(value))
2307        else:
2308            self.__model.solution_hint.vars.append(self.negated(var.index))
2309            self.__model.solution_hint.values.append(int(not value))

Adds 'var == value' as a hint to the solver.

def clear_hints(self):
2311    def clear_hints(self):
2312        """Removes any solution hint from the model."""
2313        self.__model.ClearField("solution_hint")

Removes any solution hint from the model.

def add_assumption( self, lit: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> None:
2315    def add_assumption(self, lit: LiteralT) -> None:
2316        """Adds the literal to the model as assumptions."""
2317        self.__model.assumptions.append(self.get_or_make_boolean_index(lit))

Adds the literal to the model as assumptions.

def add_assumptions( self, literals: Iterable[Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]]) -> None:
2319    def add_assumptions(self, literals: Iterable[LiteralT]) -> None:
2320        """Adds the literals to the model as assumptions."""
2321        for lit in literals:
2322            self.add_assumption(lit)

Adds the literals to the model as assumptions.

def clear_assumptions(self) -> None:
2324    def clear_assumptions(self) -> None:
2325        """Removes all assumptions from the model."""
2326        self.__model.ClearField("assumptions")

Removes all assumptions from the model.

def assert_is_boolean_variable( self, x: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> None:
2329    def assert_is_boolean_variable(self, x: LiteralT) -> None:
2330        if isinstance(x, IntVar):
2331            var = self.__model.variables[x.index]
2332            if len(var.domain) != 2 or var.domain[0] < 0 or var.domain[1] > 1:
2333                raise TypeError(
2334                    f"TypeError: {type(x).__name__!r} is not a boolean variable"
2335                )
2336        elif not isinstance(x, cmh.NotBooleanVariable):
2337            raise TypeError(
2338                f"TypeError: {type(x).__name__!r}  is not a boolean variable"
2339            )
def Name(self) -> str:
2344    def Name(self) -> str:
2345        return self.name
def SetName(self, name: str) -> None:
2347    def SetName(self, name: str) -> None:
2348        self.name = name
def Proto(self) -> ortools.sat.cp_model_pb2.CpModelProto:
2350    def Proto(self) -> cp_model_pb2.CpModelProto:
2351        return self.proto
def NewIntVar( self, lb: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], ub: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], name: str) -> IntVar:
664    def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar:
665        """Create an integer variable with domain [lb, ub].
666
667        The CP-SAT solver is limited to integer variables. If you have fractional
668        values, scale them up so that they become integers; if you have strings,
669        encode them as integers.
670
671        Args:
672          lb: Lower bound for the variable.
673          ub: Upper bound for the variable.
674          name: The name of the variable.
675
676        Returns:
677          a variable whose domain is [lb, ub].
678        """
679        domain_is_boolean = lb >= 0 and ub <= 1
680        return self._append_int_var(
681            IntVar(
682                self.__model,
683                sorted_interval_list.Domain(lb, ub),
684                domain_is_boolean,
685                name,
686            )
687        )

Create an integer variable with domain [lb, ub].

The CP-SAT solver is limited to integer variables. If you have fractional values, scale them up so that they become integers; if you have strings, encode them as integers.

Arguments:
  • lb: Lower bound for the variable.
  • ub: Upper bound for the variable.
  • name: The name of the variable.
Returns:

a variable whose domain is [lb, ub].

def NewIntVarFromDomain( self, domain: Domain, name: str) -> IntVar:
689    def new_int_var_from_domain(
690        self, domain: sorted_interval_list.Domain, name: str
691    ) -> IntVar:
692        """Create an integer variable from a domain.
693
694        A domain is a set of integers specified by a collection of intervals.
695        For example, `model.new_int_var_from_domain(cp_model.
696             Domain.from_intervals([[1, 2], [4, 6]]), 'x')`
697
698        Args:
699          domain: An instance of the Domain class.
700          name: The name of the variable.
701
702        Returns:
703            a variable whose domain is the given domain.
704        """
705        domain_is_boolean = domain.min() >= 0 and domain.max() <= 1
706        return self._append_int_var(
707            IntVar(self.__model, domain, domain_is_boolean, name)
708        )

Create an integer variable from a domain.

A domain is a set of integers specified by a collection of intervals. For example, model.new_int_var_from_domain(cp_model. Domain.from_intervals([[1, 2], [4, 6]]), 'x')

Arguments:
  • domain: An instance of the Domain class.
  • name: The name of the variable.
Returns:

a variable whose domain is the given domain.

def NewBoolVar(self, name: str) -> IntVar:
710    def new_bool_var(self, name: str) -> IntVar:
711        """Creates a 0-1 variable with the given name."""
712        return self._append_int_var(
713            IntVar(self.__model, sorted_interval_list.Domain(0, 1), True, name)
714        )

Creates a 0-1 variable with the given name.

def NewConstant( self, value: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> IntVar:
716    def new_constant(self, value: IntegralT) -> IntVar:
717        """Declares a constant integer."""
718        index: int = self.get_or_make_index_from_constant(value)
719        return self._get_int_var(index)

Declares a constant integer.

def NewIntVarSeries( self, name: str, index: pandas.core.indexes.base.Index, lower_bounds: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], upper_bounds: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series]) -> pandas.core.series.Series:
721    def new_int_var_series(
722        self,
723        name: str,
724        index: pd.Index,
725        lower_bounds: Union[IntegralT, pd.Series],
726        upper_bounds: Union[IntegralT, pd.Series],
727    ) -> pd.Series:
728        """Creates a series of (scalar-valued) variables with the given name.
729
730        Args:
731          name (str): Required. The name of the variable set.
732          index (pd.Index): Required. The index to use for the variable set.
733          lower_bounds (Union[int, pd.Series]): A lower bound for variables in the
734            set. If a `pd.Series` is passed in, it will be based on the
735            corresponding values of the pd.Series.
736          upper_bounds (Union[int, pd.Series]): An upper bound for variables in the
737            set. If a `pd.Series` is passed in, it will be based on the
738            corresponding values of the pd.Series.
739
740        Returns:
741          pd.Series: The variable set indexed by its corresponding dimensions.
742
743        Raises:
744          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
745          ValueError: if the `name` is not a valid identifier or already exists.
746          ValueError: if the `lowerbound` is greater than the `upperbound`.
747          ValueError: if the index of `lower_bound`, or `upper_bound` does not match
748          the input index.
749        """
750        if not isinstance(index, pd.Index):
751            raise TypeError("Non-index object is used as index")
752        if not name.isidentifier():
753            raise ValueError(f"name={name!r} is not a valid identifier")
754        if (
755            isinstance(lower_bounds, IntegralTypes)
756            and isinstance(upper_bounds, IntegralTypes)
757            and lower_bounds > upper_bounds
758        ):
759            raise ValueError(
760                f"lower_bound={lower_bounds} is greater than"
761                f" upper_bound={upper_bounds} for variable set={name}"
762            )
763
764        lower_bounds = _convert_to_integral_series_and_validate_index(
765            lower_bounds, index
766        )
767        upper_bounds = _convert_to_integral_series_and_validate_index(
768            upper_bounds, index
769        )
770        return pd.Series(
771            index=index,
772            data=[
773                # pylint: disable=g-complex-comprehension
774                self._append_int_var(
775                    IntVar(
776                        model=self.__model,
777                        name=f"{name}[{i}]",
778                        domain=sorted_interval_list.Domain(
779                            lower_bounds[i], upper_bounds[i]
780                        ),
781                        is_boolean=lower_bounds[i] >= 0 and upper_bounds[i] <= 1,
782                    )
783                )
784                for i in index
785            ],
786        )

Creates a series of (scalar-valued) variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • lower_bounds (Union[int, pd.Series]): A lower bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • upper_bounds (Union[int, pd.Series]): An upper bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the lowerbound is greater than the upperbound.
  • ValueError: if the index of lower_bound, or upper_bound does not match
  • the input index.
def NewBoolVarSeries( self, name: str, index: pandas.core.indexes.base.Index) -> pandas.core.series.Series:
788    def new_bool_var_series(
789        self,
790        name: str,
791        index: pd.Index,
792    ) -> pd.Series:
793        """Creates a series of (scalar-valued) variables with the given name.
794
795        Args:
796          name (str): Required. The name of the variable set.
797          index (pd.Index): Required. The index to use for the variable set.
798
799        Returns:
800          pd.Series: The variable set indexed by its corresponding dimensions.
801
802        Raises:
803          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
804          ValueError: if the `name` is not a valid identifier or already exists.
805        """
806        if not isinstance(index, pd.Index):
807            raise TypeError("Non-index object is used as index")
808        if not name.isidentifier():
809            raise ValueError(f"name={name!r} is not a valid identifier")
810        return pd.Series(
811            index=index,
812            data=[
813                # pylint: disable=g-complex-comprehension
814                self._append_int_var(
815                    IntVar(
816                        model=self.__model,
817                        name=f"{name}[{i}]",
818                        domain=sorted_interval_list.Domain(0, 1),
819                        is_boolean=True,
820                    )
821                )
822                for i in index
823            ],
824        )

Creates a series of (scalar-valued) variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
Returns:

pd.Series: The variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
def AddLinearConstraint( self, linear_expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], lb: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], ub: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
828    def add_linear_constraint(
829        self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT
830    ) -> Constraint:
831        """Adds the constraint: `lb <= linear_expr <= ub`."""
832        return self.add_linear_expression_in_domain(
833            linear_expr, sorted_interval_list.Domain(lb, ub)
834        )

Adds the constraint: lb <= linear_expr <= ub.

def AddLinearExpressionInDomain( self, linear_expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], domain: Domain) -> Constraint:
836    def add_linear_expression_in_domain(
837        self,
838        linear_expr: LinearExprT,
839        domain: sorted_interval_list.Domain,
840    ) -> Constraint:
841        """Adds the constraint: `linear_expr` in `domain`."""
842        if isinstance(linear_expr, LinearExpr):
843            ble = BoundedLinearExpression(linear_expr, domain)
844            if not ble.ok:
845                raise TypeError(
846                    "Cannot add a linear expression containing floating point"
847                    f" coefficients or constants: {type(linear_expr).__name__!r}"
848                )
849            return self.add(ble)
850        if isinstance(linear_expr, IntegralTypes):
851            if not domain.contains(int(linear_expr)):
852                return self.add_bool_or([])  # Evaluate to false.
853            else:
854                return self.add_bool_and([])  # Evaluate to true.
855        raise TypeError(
856            "not supported:"
857            f" CpModel.add_linear_expression_in_domain({type(linear_expr).__name__!r})"
858        )

Adds the constraint: linear_expr in domain.

def Add( self, ct: Union[BoundedLinearExpression, bool, numpy.bool]) -> Constraint:
860    def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint:
861        """Adds a `BoundedLinearExpression` to the model.
862
863        Args:
864          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
865
866        Returns:
867          An instance of the `Constraint` class.
868
869        Raises:
870          TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean.
871        """
872        if isinstance(ct, BoundedLinearExpression):
873            result = Constraint(self)
874            model_ct = self.__model.constraints[result.index]
875            for var in ct.vars:
876                model_ct.linear.vars.append(var.index)
877            model_ct.linear.coeffs.extend(ct.coeffs)
878            model_ct.linear.domain.extend(
879                [
880                    cmn.capped_subtraction(x, ct.offset)
881                    for x in ct.bounds.flattened_intervals()
882                ]
883            )
884            return result
885        if ct and cmn.is_boolean(ct):
886            return self.add_bool_or([True])
887        if not ct and cmn.is_boolean(ct):
888            return self.add_bool_or([])  # Evaluate to false.
889        raise TypeError(f"not supported: CpModel.add({type(ct).__name__!r})")

Adds a BoundedLinearExpression to the model.

Arguments:
Returns:

An instance of the Constraint class.

Raises:
def AddAllDifferent(self, *expressions):
899    def add_all_different(self, *expressions):
900        """Adds AllDifferent(expressions).
901
902        This constraint forces all expressions to have different values.
903
904        Args:
905          *expressions: simple expressions of the form a * var + constant.
906
907        Returns:
908          An instance of the `Constraint` class.
909        """
910        ct = Constraint(self)
911        model_ct = self.__model.constraints[ct.index]
912        expanded = expand_generator_or_tuple(expressions)
913        model_ct.all_diff.exprs.extend(
914            self.parse_linear_expression(x) for x in expanded
915        )
916        return ct

Adds AllDifferent(expressions).

This constraint forces all expressions to have different values.

Arguments:
  • *expressions: simple expressions of the form a * var + constant.
Returns:

An instance of the Constraint class.

def AddElement( self, index: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], expressions: Sequence[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
918    def add_element(
919        self,
920        index: LinearExprT,
921        expressions: Sequence[LinearExprT],
922        target: LinearExprT,
923    ) -> Constraint:
924        """Adds the element constraint: `expressions[index] == target`.
925
926        Args:
927          index: The index of the selected expression in the array. It must be an
928            affine expression (a * var + b).
929          expressions: A list of affine expressions.
930          target: The expression constrained to be equal to the selected expression.
931            It must be an affine expression (a * var + b).
932
933        Returns:
934          An instance of the `Constraint` class.
935        """
936
937        if not expressions:
938            raise ValueError("add_element expects a non-empty expressions array")
939
940        if isinstance(index, IntegralTypes):
941            expression: LinearExprT = list(expressions)[int(index)]
942            return self.add(expression == target)
943
944        ct = Constraint(self)
945        model_ct = self.__model.constraints[ct.index]
946        model_ct.element.linear_index.CopyFrom(self.parse_linear_expression(index))
947        model_ct.element.exprs.extend(
948            [self.parse_linear_expression(e) for e in expressions]
949        )
950        model_ct.element.linear_target.CopyFrom(self.parse_linear_expression(target))
951        return ct

Adds the element constraint: expressions[index] == target.

Arguments:
  • index: The index of the selected expression in the array. It must be an affine expression (a * var + b).
  • expressions: A list of affine expressions.
  • target: The expression constrained to be equal to the selected expression. It must be an affine expression (a * var + b).
Returns:

An instance of the Constraint class.

def AddCircuit( self, arcs: Sequence[Tuple[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]]]) -> Constraint:
953    def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
954        """Adds Circuit(arcs).
955
956        Adds a circuit constraint from a sparse list of arcs that encode the graph.
957
958        A circuit is a unique Hamiltonian cycle in a subgraph of the total
959        graph. In case a node 'i' is not in the cycle, then there must be a
960        loop arc 'i -> i' associated with a true literal. Otherwise
961        this constraint will fail.
962
963        Args:
964          arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
965            literal). The arc is selected in the circuit if the literal is true.
966            Both source_node and destination_node must be integers between 0 and the
967            number of nodes - 1.
968
969        Returns:
970          An instance of the `Constraint` class.
971
972        Raises:
973          ValueError: If the list of arcs is empty.
974        """
975        if not arcs:
976            raise ValueError("add_circuit expects a non-empty array of arcs")
977        ct = Constraint(self)
978        model_ct = self.__model.constraints[ct.index]
979        for arc in arcs:
980            model_ct.circuit.tails.append(arc[0])
981            model_ct.circuit.heads.append(arc[1])
982            model_ct.circuit.literals.append(self.get_or_make_boolean_index(arc[2]))
983        return ct

Adds Circuit(arcs).

Adds a circuit constraint from a sparse list of arcs that encode the graph.

A circuit is a unique Hamiltonian cycle in a subgraph of the total graph. In case a node 'i' is not in the cycle, then there must be a loop arc 'i -> i' associated with a true literal. Otherwise this constraint will fail.

Arguments:
  • arcs: a list of arcs. An arc is a tuple (source_node, destination_node, literal). The arc is selected in the circuit if the literal is true. Both source_node and destination_node must be integers between 0 and the number of nodes - 1.
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: If the list of arcs is empty.
def AddMultipleCircuit( self, arcs: Sequence[Tuple[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]]]) -> Constraint:
 985    def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint:
 986        """Adds a multiple circuit constraint, aka the 'VRP' constraint.
 987
 988        The direct graph where arc #i (from tails[i] to head[i]) is present iff
 989        literals[i] is true must satisfy this set of properties:
 990        - #incoming arcs == 1 except for node 0.
 991        - #outgoing arcs == 1 except for node 0.
 992        - for node zero, #incoming arcs == #outgoing arcs.
 993        - There are no duplicate arcs.
 994        - Self-arcs are allowed except for node 0.
 995        - There is no cycle in this graph, except through node 0.
 996
 997        Args:
 998          arcs: a list of arcs. An arc is a tuple (source_node, destination_node,
 999            literal). The arc is selected in the circuit if the literal is true.
1000            Both source_node and destination_node must be integers between 0 and the
1001            number of nodes - 1.
1002
1003        Returns:
1004          An instance of the `Constraint` class.
1005
1006        Raises:
1007          ValueError: If the list of arcs is empty.
1008        """
1009        if not arcs:
1010            raise ValueError("add_multiple_circuit expects a non-empty array of arcs")
1011        ct = Constraint(self)
1012        model_ct = self.__model.constraints[ct.index]
1013        for arc in arcs:
1014            model_ct.routes.tails.append(arc[0])
1015            model_ct.routes.heads.append(arc[1])
1016            model_ct.routes.literals.append(self.get_or_make_boolean_index(arc[2]))
1017        return ct

Adds a multiple circuit constraint, aka the 'VRP' constraint.

The direct graph where arc #i (from tails[i] to head[i]) is present iff literals[i] is true must satisfy this set of properties:

  • #incoming arcs == 1 except for node 0.
  • #outgoing arcs == 1 except for node 0.
  • for node zero, #incoming arcs == #outgoing arcs.
  • There are no duplicate arcs.
  • Self-arcs are allowed except for node 0.
  • There is no cycle in this graph, except through node 0.
Arguments:
  • arcs: a list of arcs. An arc is a tuple (source_node, destination_node, literal). The arc is selected in the circuit if the literal is true. Both source_node and destination_node must be integers between 0 and the number of nodes - 1.
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: If the list of arcs is empty.
def AddAllowedAssignments( self, expressions: Sequence[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], tuples_list: Iterable[Sequence[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]]) -> Constraint:
1019    def add_allowed_assignments(
1020        self,
1021        expressions: Sequence[LinearExprT],
1022        tuples_list: Iterable[Sequence[IntegralT]],
1023    ) -> Constraint:
1024        """Adds AllowedAssignments(expressions, tuples_list).
1025
1026        An AllowedAssignments constraint is a constraint on an array of affine
1027        expressions, which requires that when all expressions are assigned values,
1028        the
1029        resulting array equals one of the  tuples in `tuple_list`.
1030
1031        Args:
1032          expressions: A list of affine expressions (a * var + b).
1033          tuples_list: A list of admissible tuples. Each tuple must have the same
1034            length as the expressions, and the ith value of a tuple corresponds to
1035            the ith expression.
1036
1037        Returns:
1038          An instance of the `Constraint` class.
1039
1040        Raises:
1041          TypeError: If a tuple does not have the same size as the list of
1042              expressions.
1043          ValueError: If the array of expressions is empty.
1044        """
1045
1046        if not expressions:
1047            raise ValueError(
1048                "add_allowed_assignments expects a non-empty expressions array"
1049            )
1050
1051        ct: Constraint = Constraint(self)
1052        model_ct = self.__model.constraints[ct.index]
1053        model_ct.table.exprs.extend(
1054            [self.parse_linear_expression(e) for e in expressions]
1055        )
1056        arity: int = len(expressions)
1057        for one_tuple in tuples_list:
1058            if len(one_tuple) != arity:
1059                raise TypeError(f"Tuple {one_tuple!r} has the wrong arity")
1060
1061        # duck-typing (no explicit type checks here)
1062        try:
1063            for one_tuple in tuples_list:
1064                model_ct.table.values.extend(one_tuple)
1065        except ValueError as ex:
1066            raise TypeError(
1067                "add_xxx_assignment: Not an integer or does not fit in an int64_t:"
1068                f" {type(ex.args).__name__!r}"
1069            ) from ex
1070
1071        return ct

Adds AllowedAssignments(expressions, tuples_list).

An AllowedAssignments constraint is a constraint on an array of affine expressions, which requires that when all expressions are assigned values, the resulting array equals one of the tuples in tuple_list.

Arguments:
  • expressions: A list of affine expressions (a * var + b).
  • tuples_list: A list of admissible tuples. Each tuple must have the same length as the expressions, and the ith value of a tuple corresponds to the ith expression.
Returns:

An instance of the Constraint class.

Raises:
  • TypeError: If a tuple does not have the same size as the list of expressions.
  • ValueError: If the array of expressions is empty.
def AddForbiddenAssignments( self, expressions: Sequence[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], tuples_list: Iterable[Sequence[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]]) -> Constraint:
1073    def add_forbidden_assignments(
1074        self,
1075        expressions: Sequence[LinearExprT],
1076        tuples_list: Iterable[Sequence[IntegralT]],
1077    ) -> Constraint:
1078        """Adds add_forbidden_assignments(expressions, [tuples_list]).
1079
1080        A ForbiddenAssignments constraint is a constraint on an array of affine
1081        expressions where the list of impossible combinations is provided in the
1082        tuples list.
1083
1084        Args:
1085          expressions: A list of affine expressions (a * var + b).
1086          tuples_list: A list of forbidden tuples. Each tuple must have the same
1087            length as the expressions, and the *i*th value of a tuple corresponds to
1088            the *i*th expression.
1089
1090        Returns:
1091          An instance of the `Constraint` class.
1092
1093        Raises:
1094          TypeError: If a tuple does not have the same size as the list of
1095                     expressions.
1096          ValueError: If the array of expressions is empty.
1097        """
1098
1099        if not expressions:
1100            raise ValueError(
1101                "add_forbidden_assignments expects a non-empty expressions array"
1102            )
1103
1104        index: int = len(self.__model.constraints)
1105        ct: Constraint = self.add_allowed_assignments(expressions, tuples_list)
1106        self.__model.constraints[index].table.negated = True
1107        return ct

Adds add_forbidden_assignments(expressions, [tuples_list]).

A ForbiddenAssignments constraint is a constraint on an array of affine expressions where the list of impossible combinations is provided in the tuples list.

Arguments:
  • expressions: A list of affine expressions (a * var + b).
  • tuples_list: A list of forbidden tuples. Each tuple must have the same length as the expressions, and the ith value of a tuple corresponds to the ith expression.
Returns:

An instance of the Constraint class.

Raises:
  • TypeError: If a tuple does not have the same size as the list of expressions.
  • ValueError: If the array of expressions is empty.
def AddAutomaton( self, transition_expressions: Sequence[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], starting_state: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], final_states: Sequence[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], transition_triples: Sequence[Tuple[Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]]) -> Constraint:
1109    def add_automaton(
1110        self,
1111        transition_expressions: Sequence[LinearExprT],
1112        starting_state: IntegralT,
1113        final_states: Sequence[IntegralT],
1114        transition_triples: Sequence[Tuple[IntegralT, IntegralT, IntegralT]],
1115    ) -> Constraint:
1116        """Adds an automaton constraint.
1117
1118        An automaton constraint takes a list of affine expressions (a * var + b) (of
1119        size *n*), an initial state, a set of final states, and a set of
1120        transitions. A transition is a triplet (*tail*, *transition*, *head*), where
1121        *tail* and *head* are states, and *transition* is the label of an arc from
1122        *head* to *tail*, corresponding to the value of one expression in the list
1123        of
1124        expressions.
1125
1126        This automaton will be unrolled into a flow with *n* + 1 phases. Each phase
1127        contains the possible states of the automaton. The first state contains the
1128        initial state. The last phase contains the final states.
1129
1130        Between two consecutive phases *i* and *i* + 1, the automaton creates a set
1131        of arcs. For each transition (*tail*, *transition*, *head*), it will add
1132        an arc from the state *tail* of phase *i* and the state *head* of phase
1133        *i* + 1. This arc is labeled by the value *transition* of the expression
1134        `expressions[i]`. That is, this arc can only be selected if `expressions[i]`
1135        is assigned the value *transition*.
1136
1137        A feasible solution of this constraint is an assignment of expressions such
1138        that, starting from the initial state in phase 0, there is a path labeled by
1139        the values of the expressions that ends in one of the final states in the
1140        final phase.
1141
1142        Args:
1143          transition_expressions: A non-empty list of affine expressions (a * var +
1144            b) whose values correspond to the labels of the arcs traversed by the
1145            automaton.
1146          starting_state: The initial state of the automaton.
1147          final_states: A non-empty list of admissible final states.
1148          transition_triples: A list of transitions for the automaton, in the
1149            following format (current_state, variable_value, next_state).
1150
1151        Returns:
1152          An instance of the `Constraint` class.
1153
1154        Raises:
1155          ValueError: if `transition_expressions`, `final_states`, or
1156            `transition_triples` are empty.
1157        """
1158
1159        if not transition_expressions:
1160            raise ValueError(
1161                "add_automaton expects a non-empty transition_expressions array"
1162            )
1163        if not final_states:
1164            raise ValueError("add_automaton expects some final states")
1165
1166        if not transition_triples:
1167            raise ValueError("add_automaton expects some transition triples")
1168
1169        ct = Constraint(self)
1170        model_ct = self.__model.constraints[ct.index]
1171        model_ct.automaton.exprs.extend(
1172            [self.parse_linear_expression(e) for e in transition_expressions]
1173        )
1174        model_ct.automaton.starting_state = starting_state
1175        for v in final_states:
1176            model_ct.automaton.final_states.append(v)
1177        for t in transition_triples:
1178            if len(t) != 3:
1179                raise TypeError(f"Tuple {t!r} has the wrong arity (!= 3)")
1180            model_ct.automaton.transition_tail.append(t[0])
1181            model_ct.automaton.transition_label.append(t[1])
1182            model_ct.automaton.transition_head.append(t[2])
1183        return ct

Adds an automaton constraint.

An automaton constraint takes a list of affine expressions (a * var + b) (of size n), an initial state, a set of final states, and a set of transitions. A transition is a triplet (tail, transition, head), where tail and head are states, and transition is the label of an arc from head to tail, corresponding to the value of one expression in the list of expressions.

This automaton will be unrolled into a flow with n + 1 phases. Each phase contains the possible states of the automaton. The first state contains the initial state. The last phase contains the final states.

Between two consecutive phases i and i + 1, the automaton creates a set of arcs. For each transition (tail, transition, head), it will add an arc from the state tail of phase i and the state head of phase i + 1. This arc is labeled by the value transition of the expression expressions[i]. That is, this arc can only be selected if expressions[i] is assigned the value transition.

A feasible solution of this constraint is an assignment of expressions such that, starting from the initial state in phase 0, there is a path labeled by the values of the expressions that ends in one of the final states in the final phase.

Arguments:
  • transition_expressions: A non-empty list of affine expressions (a * var + b) whose values correspond to the labels of the arcs traversed by the automaton.
  • starting_state: The initial state of the automaton.
  • final_states: A non-empty list of admissible final states.
  • transition_triples: A list of transitions for the automaton, in the following format (current_state, variable_value, next_state).
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: if transition_expressions, final_states, or transition_triples are empty.
def AddInverse( self, variables: Sequence[Union[IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], inverse_variables: Sequence[Union[IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]) -> Constraint:
1185    def add_inverse(
1186        self,
1187        variables: Sequence[VariableT],
1188        inverse_variables: Sequence[VariableT],
1189    ) -> Constraint:
1190        """Adds Inverse(variables, inverse_variables).
1191
1192        An inverse constraint enforces that if `variables[i]` is assigned a value
1193        `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa.
1194
1195        Args:
1196          variables: An array of integer variables.
1197          inverse_variables: An array of integer variables.
1198
1199        Returns:
1200          An instance of the `Constraint` class.
1201
1202        Raises:
1203          TypeError: if variables and inverse_variables have different lengths, or
1204              if they are empty.
1205        """
1206
1207        if not variables or not inverse_variables:
1208            raise TypeError("The Inverse constraint does not accept empty arrays")
1209        if len(variables) != len(inverse_variables):
1210            raise TypeError(
1211                "In the inverse constraint, the two array variables and"
1212                " inverse_variables must have the same length."
1213            )
1214        ct = Constraint(self)
1215        model_ct = self.__model.constraints[ct.index]
1216        model_ct.inverse.f_direct.extend([self.get_or_make_index(x) for x in variables])
1217        model_ct.inverse.f_inverse.extend(
1218            [self.get_or_make_index(x) for x in inverse_variables]
1219        )
1220        return ct

Adds Inverse(variables, inverse_variables).

An inverse constraint enforces that if variables[i] is assigned a value j, then inverse_variables[j] is assigned a value i. And vice versa.

Arguments:
  • variables: An array of integer variables.
  • inverse_variables: An array of integer variables.
Returns:

An instance of the Constraint class.

Raises:
  • TypeError: if variables and inverse_variables have different lengths, or if they are empty.
def AddReservoirConstraint( self, times: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], level_changes: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], min_level: int, max_level: int) -> Constraint:
1222    def add_reservoir_constraint(
1223        self,
1224        times: Iterable[LinearExprT],
1225        level_changes: Iterable[LinearExprT],
1226        min_level: int,
1227        max_level: int,
1228    ) -> Constraint:
1229        """Adds Reservoir(times, level_changes, min_level, max_level).
1230
1231        Maintains a reservoir level within bounds. The water level starts at 0, and
1232        at any time, it must be between min_level and max_level.
1233
1234        If the affine expression `times[i]` is assigned a value t, then the current
1235        level changes by `level_changes[i]`, which is constant, at time t.
1236
1237         Note that min level must be <= 0, and the max level must be >= 0. Please
1238         use fixed level_changes to simulate initial state.
1239
1240         Therefore, at any time:
1241             sum(level_changes[i] if times[i] <= t) in [min_level, max_level]
1242
1243        Args:
1244          times: A list of 1-var affine expressions (a * x + b) which specify the
1245            time of the filling or emptying the reservoir.
1246          level_changes: A list of integer values that specifies the amount of the
1247            emptying or filling. Currently, variable demands are not supported.
1248          min_level: At any time, the level of the reservoir must be greater or
1249            equal than the min level.
1250          max_level: At any time, the level of the reservoir must be less or equal
1251            than the max level.
1252
1253        Returns:
1254          An instance of the `Constraint` class.
1255
1256        Raises:
1257          ValueError: if max_level < min_level.
1258
1259          ValueError: if max_level < 0.
1260
1261          ValueError: if min_level > 0
1262        """
1263
1264        if max_level < min_level:
1265            raise ValueError("Reservoir constraint must have a max_level >= min_level")
1266
1267        if max_level < 0:
1268            raise ValueError("Reservoir constraint must have a max_level >= 0")
1269
1270        if min_level > 0:
1271            raise ValueError("Reservoir constraint must have a min_level <= 0")
1272
1273        ct = Constraint(self)
1274        model_ct = self.__model.constraints[ct.index]
1275        model_ct.reservoir.time_exprs.extend(
1276            [self.parse_linear_expression(x) for x in times]
1277        )
1278        model_ct.reservoir.level_changes.extend(
1279            [self.parse_linear_expression(x) for x in level_changes]
1280        )
1281        model_ct.reservoir.min_level = min_level
1282        model_ct.reservoir.max_level = max_level
1283        return ct

Adds Reservoir(times, level_changes, min_level, max_level).

Maintains a reservoir level within bounds. The water level starts at 0, and at any time, it must be between min_level and max_level.

If the affine expression times[i] is assigned a value t, then the current level changes by level_changes[i], which is constant, at time t.

Note that min level must be <= 0, and the max level must be >= 0. Please use fixed level_changes to simulate initial state.

Therefore, at any time: sum(level_changes[i] if times[i] <= t) in [min_level, max_level]

Arguments:
  • times: A list of 1-var affine expressions (a * x + b) which specify the time of the filling or emptying the reservoir.
  • level_changes: A list of integer values that specifies the amount of the emptying or filling. Currently, variable demands are not supported.
  • min_level: At any time, the level of the reservoir must be greater or equal than the min level.
  • max_level: At any time, the level of the reservoir must be less or equal than the max level.
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: if max_level < min_level.
  • ValueError: if max_level < 0.
  • ValueError: if min_level > 0
def AddReservoirConstraintWithActive( self, times: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], level_changes: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], actives: Iterable[Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]], min_level: int, max_level: int) -> Constraint:
1285    def add_reservoir_constraint_with_active(
1286        self,
1287        times: Iterable[LinearExprT],
1288        level_changes: Iterable[LinearExprT],
1289        actives: Iterable[LiteralT],
1290        min_level: int,
1291        max_level: int,
1292    ) -> Constraint:
1293        """Adds Reservoir(times, level_changes, actives, min_level, max_level).
1294
1295        Maintains a reservoir level within bounds. The water level starts at 0, and
1296        at any time, it must be between min_level and max_level.
1297
1298        If the variable `times[i]` is assigned a value t, and `actives[i]` is
1299        `True`, then the current level changes by `level_changes[i]`, which is
1300        constant,
1301        at time t.
1302
1303         Note that min level must be <= 0, and the max level must be >= 0. Please
1304         use fixed level_changes to simulate initial state.
1305
1306         Therefore, at any time:
1307             sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level,
1308             max_level]
1309
1310
1311        The array of boolean variables 'actives', if defined, indicates which
1312        actions are actually performed.
1313
1314        Args:
1315          times: A list of 1-var affine expressions (a * x + b) which specify the
1316            time of the filling or emptying the reservoir.
1317          level_changes: A list of integer values that specifies the amount of the
1318            emptying or filling. Currently, variable demands are not supported.
1319          actives: a list of boolean variables. They indicates if the
1320            emptying/refilling events actually take place.
1321          min_level: At any time, the level of the reservoir must be greater or
1322            equal than the min level.
1323          max_level: At any time, the level of the reservoir must be less or equal
1324            than the max level.
1325
1326        Returns:
1327          An instance of the `Constraint` class.
1328
1329        Raises:
1330          ValueError: if max_level < min_level.
1331
1332          ValueError: if max_level < 0.
1333
1334          ValueError: if min_level > 0
1335        """
1336
1337        if max_level < min_level:
1338            raise ValueError("Reservoir constraint must have a max_level >= min_level")
1339
1340        if max_level < 0:
1341            raise ValueError("Reservoir constraint must have a max_level >= 0")
1342
1343        if min_level > 0:
1344            raise ValueError("Reservoir constraint must have a min_level <= 0")
1345
1346        ct = Constraint(self)
1347        model_ct = self.__model.constraints[ct.index]
1348        model_ct.reservoir.time_exprs.extend(
1349            [self.parse_linear_expression(x) for x in times]
1350        )
1351        model_ct.reservoir.level_changes.extend(
1352            [self.parse_linear_expression(x) for x in level_changes]
1353        )
1354        model_ct.reservoir.active_literals.extend(
1355            [self.get_or_make_boolean_index(x) for x in actives]
1356        )
1357        model_ct.reservoir.min_level = min_level
1358        model_ct.reservoir.max_level = max_level
1359        return ct

Adds Reservoir(times, level_changes, actives, min_level, max_level).

Maintains a reservoir level within bounds. The water level starts at 0, and at any time, it must be between min_level and max_level.

If the variable times[i] is assigned a value t, and actives[i] is True, then the current level changes by level_changes[i], which is constant, at time t.

Note that min level must be <= 0, and the max level must be >= 0. Please use fixed level_changes to simulate initial state.

Therefore, at any time: sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level, max_level]

The array of boolean variables 'actives', if defined, indicates which actions are actually performed.

Arguments:
  • times: A list of 1-var affine expressions (a * x + b) which specify the time of the filling or emptying the reservoir.
  • level_changes: A list of integer values that specifies the amount of the emptying or filling. Currently, variable demands are not supported.
  • actives: a list of boolean variables. They indicates if the emptying/refilling events actually take place.
  • min_level: At any time, the level of the reservoir must be greater or equal than the min level.
  • max_level: At any time, the level of the reservoir must be less or equal than the max level.
Returns:

An instance of the Constraint class.

Raises:
  • ValueError: if max_level < min_level.
  • ValueError: if max_level < 0.
  • ValueError: if min_level > 0
def AddImplication( self, a: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool], b: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> Constraint:
1385    def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint:
1386        """Adds `a => b` (`a` implies `b`)."""
1387        ct = Constraint(self)
1388        model_ct = self.__model.constraints[ct.index]
1389        model_ct.bool_or.literals.append(self.get_or_make_boolean_index(b))
1390        model_ct.enforcement_literal.append(self.get_or_make_boolean_index(a))
1391        return ct

Adds a => b (a implies b).

def AddBoolOr(self, *literals):
1399    def add_bool_or(self, *literals):
1400        """Adds `Or(literals) == true`: sum(literals) >= 1."""
1401        ct = Constraint(self)
1402        model_ct = self.__model.constraints[ct.index]
1403        model_ct.bool_or.literals.extend(
1404            [
1405                self.get_or_make_boolean_index(x)
1406                for x in expand_generator_or_tuple(literals)
1407            ]
1408        )
1409        return ct

Adds Or(literals) == true: sum(literals) >= 1.

def AddAtLeastOne(self, *literals):
1417    def add_at_least_one(self, *literals):
1418        """Same as `add_bool_or`: `sum(literals) >= 1`."""
1419        return self.add_bool_or(*literals)

Same as add_bool_or: sum(literals) >= 1.

def AddAtMostOne(self, *literals):
1427    def add_at_most_one(self, *literals):
1428        """Adds `AtMostOne(literals)`: `sum(literals) <= 1`."""
1429        ct = Constraint(self)
1430        model_ct = self.__model.constraints[ct.index]
1431        model_ct.at_most_one.literals.extend(
1432            [
1433                self.get_or_make_boolean_index(x)
1434                for x in expand_generator_or_tuple(literals)
1435            ]
1436        )
1437        return ct

Adds AtMostOne(literals): sum(literals) <= 1.

def AddExactlyOne(self, *literals):
1445    def add_exactly_one(self, *literals):
1446        """Adds `ExactlyOne(literals)`: `sum(literals) == 1`."""
1447        ct = Constraint(self)
1448        model_ct = self.__model.constraints[ct.index]
1449        model_ct.exactly_one.literals.extend(
1450            [
1451                self.get_or_make_boolean_index(x)
1452                for x in expand_generator_or_tuple(literals)
1453            ]
1454        )
1455        return ct

Adds ExactlyOne(literals): sum(literals) == 1.

def AddBoolAnd(self, *literals):
1463    def add_bool_and(self, *literals):
1464        """Adds `And(literals) == true`."""
1465        ct = Constraint(self)
1466        model_ct = self.__model.constraints[ct.index]
1467        model_ct.bool_and.literals.extend(
1468            [
1469                self.get_or_make_boolean_index(x)
1470                for x in expand_generator_or_tuple(literals)
1471            ]
1472        )
1473        return ct

Adds And(literals) == true.

def AddBoolXOr(self, *literals):
1481    def add_bool_xor(self, *literals):
1482        """Adds `XOr(literals) == true`.
1483
1484        In contrast to add_bool_or and add_bool_and, it does not support
1485            .only_enforce_if().
1486
1487        Args:
1488          *literals: the list of literals in the constraint.
1489
1490        Returns:
1491          An `Constraint` object.
1492        """
1493        ct = Constraint(self)
1494        model_ct = self.__model.constraints[ct.index]
1495        model_ct.bool_xor.literals.extend(
1496            [
1497                self.get_or_make_boolean_index(x)
1498                for x in expand_generator_or_tuple(literals)
1499            ]
1500        )
1501        return ct

Adds XOr(literals) == true.

In contrast to add_bool_or and add_bool_and, it does not support .only_enforce_if().

Arguments:
  • *literals: the list of literals in the constraint.
Returns:

An Constraint object.

def AddMinEquality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], exprs: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]) -> Constraint:
1503    def add_min_equality(
1504        self, target: LinearExprT, exprs: Iterable[LinearExprT]
1505    ) -> Constraint:
1506        """Adds `target == Min(exprs)`."""
1507        ct = Constraint(self)
1508        model_ct = self.__model.constraints[ct.index]
1509        model_ct.lin_max.exprs.extend(
1510            [self.parse_linear_expression(x, True) for x in exprs]
1511        )
1512        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target, True))
1513        return ct

Adds target == Min(exprs).

def AddMaxEquality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], exprs: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]]) -> Constraint:
1515    def add_max_equality(
1516        self, target: LinearExprT, exprs: Iterable[LinearExprT]
1517    ) -> Constraint:
1518        """Adds `target == Max(exprs)`."""
1519        ct = Constraint(self)
1520        model_ct = self.__model.constraints[ct.index]
1521        model_ct.lin_max.exprs.extend([self.parse_linear_expression(x) for x in exprs])
1522        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
1523        return ct

Adds target == Max(exprs).

def AddDivisionEquality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], num: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], denom: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
1525    def add_division_equality(
1526        self, target: LinearExprT, num: LinearExprT, denom: LinearExprT
1527    ) -> Constraint:
1528        """Adds `target == num // denom` (integer division rounded towards 0)."""
1529        ct = Constraint(self)
1530        model_ct = self.__model.constraints[ct.index]
1531        model_ct.int_div.exprs.append(self.parse_linear_expression(num))
1532        model_ct.int_div.exprs.append(self.parse_linear_expression(denom))
1533        model_ct.int_div.target.CopyFrom(self.parse_linear_expression(target))
1534        return ct

Adds target == num // denom (integer division rounded towards 0).

def AddAbsEquality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
1536    def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint:
1537        """Adds `target == Abs(expr)`."""
1538        ct = Constraint(self)
1539        model_ct = self.__model.constraints[ct.index]
1540        model_ct.lin_max.exprs.append(self.parse_linear_expression(expr))
1541        model_ct.lin_max.exprs.append(self.parse_linear_expression(expr, True))
1542        model_ct.lin_max.target.CopyFrom(self.parse_linear_expression(target))
1543        return ct

Adds target == Abs(expr).

def AddModuloEquality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], expr: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], mod: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
1545    def add_modulo_equality(
1546        self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT
1547    ) -> Constraint:
1548        """Adds `target = expr % mod`.
1549
1550        It uses the C convention, that is the result is the remainder of the
1551        integral division rounded towards 0.
1552
1553            For example:
1554            * 10 % 3 = 1
1555            * -10 % 3 = -1
1556            * 10 % -3 = 1
1557            * -10 % -3 = -1
1558
1559        Args:
1560          target: the target expression.
1561          expr: the expression to compute the modulo of.
1562          mod: the modulus expression.
1563
1564        Returns:
1565          A `Constraint` object.
1566        """
1567        ct = Constraint(self)
1568        model_ct = self.__model.constraints[ct.index]
1569        model_ct.int_mod.exprs.append(self.parse_linear_expression(expr))
1570        model_ct.int_mod.exprs.append(self.parse_linear_expression(mod))
1571        model_ct.int_mod.target.CopyFrom(self.parse_linear_expression(target))
1572        return ct

Adds target = expr % mod.

It uses the C convention, that is the result is the remainder of the integral division rounded towards 0.

For example:
* 10 % 3 = 1
* -10 % 3 = -1
* 10 % -3 = 1
* -10 % -3 = -1
Arguments:
  • target: the target expression.
  • expr: the expression to compute the modulo of.
  • mod: the modulus expression.
Returns:

A Constraint object.

def AddMultiplicationEquality( self, target: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], *expressions: Union[Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
1574    def add_multiplication_equality(
1575        self,
1576        target: LinearExprT,
1577        *expressions: Union[Iterable[LinearExprT], LinearExprT],
1578    ) -> Constraint:
1579        """Adds `target == expressions[0] * .. * expressions[n]`."""
1580        ct = Constraint(self)
1581        model_ct = self.__model.constraints[ct.index]
1582        model_ct.int_prod.exprs.extend(
1583            [
1584                self.parse_linear_expression(expr)
1585                for expr in expand_generator_or_tuple(expressions)
1586            ]
1587        )
1588        model_ct.int_prod.target.CopyFrom(self.parse_linear_expression(target))
1589        return ct

Adds target == expressions[0] * .. * expressions[n].

def NewIntervalVar( self, start: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], size: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], end: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], name: str) -> IntervalVar:
1593    def new_interval_var(
1594        self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str
1595    ) -> IntervalVar:
1596        """Creates an interval variable from start, size, and end.
1597
1598        An interval variable is a constraint, that is itself used in other
1599        constraints like NoOverlap.
1600
1601        Internally, it ensures that `start + size == end`.
1602
1603        Args:
1604          start: The start of the interval. It must be of the form a * var + b.
1605          size: The size of the interval. It must be of the form a * var + b.
1606          end: The end of the interval. It must be of the form a * var + b.
1607          name: The name of the interval variable.
1608
1609        Returns:
1610          An `IntervalVar` object.
1611        """
1612
1613        start_expr = self.parse_linear_expression(start)
1614        size_expr = self.parse_linear_expression(size)
1615        end_expr = self.parse_linear_expression(end)
1616        if len(start_expr.vars) > 1:
1617            raise TypeError(
1618                "cp_model.new_interval_var: start must be 1-var affine or constant."
1619            )
1620        if len(size_expr.vars) > 1:
1621            raise TypeError(
1622                "cp_model.new_interval_var: size must be 1-var affine or constant."
1623            )
1624        if len(end_expr.vars) > 1:
1625            raise TypeError(
1626                "cp_model.new_interval_var: end must be 1-var affine or constant."
1627            )
1628        return IntervalVar(
1629            self.__model,
1630            self.__var_list,
1631            start_expr,
1632            size_expr,
1633            end_expr,
1634            None,
1635            name,
1636        )

Creates an interval variable from start, size, and end.

An interval variable is a constraint, that is itself used in other constraints like NoOverlap.

Internally, it ensures that start + size == end.

Arguments:
  • start: The start of the interval. It must be of the form a * var + b.
  • size: The size of the interval. It must be of the form a * var + b.
  • end: The end of the interval. It must be of the form a * var + b.
  • name: The name of the interval variable.
Returns:

An IntervalVar object.

def NewIntervalVarSeries( self, name: str, index: pandas.core.indexes.base.Index, starts: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], sizes: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], ends: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series]) -> pandas.core.series.Series:
1638    def new_interval_var_series(
1639        self,
1640        name: str,
1641        index: pd.Index,
1642        starts: Union[LinearExprT, pd.Series],
1643        sizes: Union[LinearExprT, pd.Series],
1644        ends: Union[LinearExprT, pd.Series],
1645    ) -> pd.Series:
1646        """Creates a series of interval variables with the given name.
1647
1648        Args:
1649          name (str): Required. The name of the variable set.
1650          index (pd.Index): Required. The index to use for the variable set.
1651          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1652            set. If a `pd.Series` is passed in, it will be based on the
1653            corresponding values of the pd.Series.
1654          sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1655            set. If a `pd.Series` is passed in, it will be based on the
1656            corresponding values of the pd.Series.
1657          ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1658            set. If a `pd.Series` is passed in, it will be based on the
1659            corresponding values of the pd.Series.
1660
1661        Returns:
1662          pd.Series: The interval variable set indexed by its corresponding
1663          dimensions.
1664
1665        Raises:
1666          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1667          ValueError: if the `name` is not a valid identifier or already exists.
1668          ValueError: if the all the indexes do not match.
1669        """
1670        if not isinstance(index, pd.Index):
1671            raise TypeError("Non-index object is used as index")
1672        if not name.isidentifier():
1673            raise ValueError(f"name={name!r} is not a valid identifier")
1674
1675        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1676        sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
1677        ends = _convert_to_linear_expr_series_and_validate_index(ends, index)
1678        interval_array = []
1679        for i in index:
1680            interval_array.append(
1681                self.new_interval_var(
1682                    start=starts[i],
1683                    size=sizes[i],
1684                    end=ends[i],
1685                    name=f"{name}[{i}]",
1686                )
1687            )
1688        return pd.Series(index=index, data=interval_array)

Creates a series of interval variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • starts (Union[LinearExprT, pd.Series]): The start of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • sizes (Union[LinearExprT, pd.Series]): The size of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • ends (Union[LinearExprT, pd.Series]): The ends of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The interval variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the all the indexes do not match.
def NewFixedSizeIntervalVar( self, start: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], size: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], name: str) -> IntervalVar:
1690    def new_fixed_size_interval_var(
1691        self, start: LinearExprT, size: IntegralT, name: str
1692    ) -> IntervalVar:
1693        """Creates an interval variable from start, and a fixed size.
1694
1695        An interval variable is a constraint, that is itself used in other
1696        constraints like NoOverlap.
1697
1698        Args:
1699          start: The start of the interval. It must be of the form a * var + b.
1700          size: The size of the interval. It must be an integer value.
1701          name: The name of the interval variable.
1702
1703        Returns:
1704          An `IntervalVar` object.
1705        """
1706        start_expr = self.parse_linear_expression(start)
1707        size_expr = self.parse_linear_expression(size)
1708        end_expr = self.parse_linear_expression(start + size)
1709        if len(start_expr.vars) > 1:
1710            raise TypeError(
1711                "cp_model.new_interval_var: start must be affine or constant."
1712            )
1713        return IntervalVar(
1714            self.__model,
1715            self.__var_list,
1716            start_expr,
1717            size_expr,
1718            end_expr,
1719            None,
1720            name,
1721        )

Creates an interval variable from start, and a fixed size.

An interval variable is a constraint, that is itself used in other constraints like NoOverlap.

Arguments:
  • start: The start of the interval. It must be of the form a * var + b.
  • size: The size of the interval. It must be an integer value.
  • name: The name of the interval variable.
Returns:

An IntervalVar object.

def NewOptionalIntervalVar( self, start: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], size: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], end: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], is_present: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool], name: str) -> IntervalVar:
1769    def new_optional_interval_var(
1770        self,
1771        start: LinearExprT,
1772        size: LinearExprT,
1773        end: LinearExprT,
1774        is_present: LiteralT,
1775        name: str,
1776    ) -> IntervalVar:
1777        """Creates an optional interval var from start, size, end, and is_present.
1778
1779        An optional interval variable is a constraint, that is itself used in other
1780        constraints like NoOverlap. This constraint is protected by a presence
1781        literal that indicates if it is active or not.
1782
1783        Internally, it ensures that `is_present` implies `start + size ==
1784        end`.
1785
1786        Args:
1787          start: The start of the interval. It must be of the form a * var + b.
1788          size: The size of the interval. It must be of the form a * var + b.
1789          end: The end of the interval. It must be of the form a * var + b.
1790          is_present: A literal that indicates if the interval is active or not. A
1791            inactive interval is simply ignored by all constraints.
1792          name: The name of the interval variable.
1793
1794        Returns:
1795          An `IntervalVar` object.
1796        """
1797
1798        # Creates the IntervalConstraintProto object.
1799        is_present_index = self.get_or_make_boolean_index(is_present)
1800        start_expr = self.parse_linear_expression(start)
1801        size_expr = self.parse_linear_expression(size)
1802        end_expr = self.parse_linear_expression(end)
1803        if len(start_expr.vars) > 1:
1804            raise TypeError(
1805                "cp_model.new_interval_var: start must be affine or constant."
1806            )
1807        if len(size_expr.vars) > 1:
1808            raise TypeError(
1809                "cp_model.new_interval_var: size must be affine or constant."
1810            )
1811        if len(end_expr.vars) > 1:
1812            raise TypeError(
1813                "cp_model.new_interval_var: end must be affine or constant."
1814            )
1815        return IntervalVar(
1816            self.__model,
1817            self.__var_list,
1818            start_expr,
1819            size_expr,
1820            end_expr,
1821            is_present_index,
1822            name,
1823        )

Creates an optional interval var from start, size, end, and is_present.

An optional interval variable is a constraint, that is itself used in other constraints like NoOverlap. This constraint is protected by a presence literal that indicates if it is active or not.

Internally, it ensures that is_present implies start + size == end.

Arguments:
  • start: The start of the interval. It must be of the form a * var + b.
  • size: The size of the interval. It must be of the form a * var + b.
  • end: The end of the interval. It must be of the form a * var + b.
  • is_present: A literal that indicates if the interval is active or not. A inactive interval is simply ignored by all constraints.
  • name: The name of the interval variable.
Returns:

An IntervalVar object.

def NewOptionalIntervalVarSeries( self, name: str, index: pandas.core.indexes.base.Index, starts: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], sizes: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], ends: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], are_present: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool, pandas.core.series.Series]) -> pandas.core.series.Series:
1825    def new_optional_interval_var_series(
1826        self,
1827        name: str,
1828        index: pd.Index,
1829        starts: Union[LinearExprT, pd.Series],
1830        sizes: Union[LinearExprT, pd.Series],
1831        ends: Union[LinearExprT, pd.Series],
1832        are_present: Union[LiteralT, pd.Series],
1833    ) -> pd.Series:
1834        """Creates a series of interval variables with the given name.
1835
1836        Args:
1837          name (str): Required. The name of the variable set.
1838          index (pd.Index): Required. The index to use for the variable set.
1839          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1840            set. If a `pd.Series` is passed in, it will be based on the
1841            corresponding values of the pd.Series.
1842          sizes (Union[LinearExprT, pd.Series]): The size of each interval in the
1843            set. If a `pd.Series` is passed in, it will be based on the
1844            corresponding values of the pd.Series.
1845          ends (Union[LinearExprT, pd.Series]): The ends of each interval in the
1846            set. If a `pd.Series` is passed in, it will be based on the
1847            corresponding values of the pd.Series.
1848          are_present (Union[LiteralT, pd.Series]): The performed literal of each
1849            interval in the set. If a `pd.Series` is passed in, it will be based on
1850            the corresponding values of the pd.Series.
1851
1852        Returns:
1853          pd.Series: The interval variable set indexed by its corresponding
1854          dimensions.
1855
1856        Raises:
1857          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1858          ValueError: if the `name` is not a valid identifier or already exists.
1859          ValueError: if the all the indexes do not match.
1860        """
1861        if not isinstance(index, pd.Index):
1862            raise TypeError("Non-index object is used as index")
1863        if not name.isidentifier():
1864            raise ValueError(f"name={name!r} is not a valid identifier")
1865
1866        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1867        sizes = _convert_to_linear_expr_series_and_validate_index(sizes, index)
1868        ends = _convert_to_linear_expr_series_and_validate_index(ends, index)
1869        are_present = _convert_to_literal_series_and_validate_index(are_present, index)
1870
1871        interval_array = []
1872        for i in index:
1873            interval_array.append(
1874                self.new_optional_interval_var(
1875                    start=starts[i],
1876                    size=sizes[i],
1877                    end=ends[i],
1878                    is_present=are_present[i],
1879                    name=f"{name}[{i}]",
1880                )
1881            )
1882        return pd.Series(index=index, data=interval_array)

Creates a series of interval variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • starts (Union[LinearExprT, pd.Series]): The start of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • sizes (Union[LinearExprT, pd.Series]): The size of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • ends (Union[LinearExprT, pd.Series]): The ends of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • are_present (Union[LiteralT, pd.Series]): The performed literal of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The interval variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the all the indexes do not match.
def NewOptionalFixedSizeIntervalVar( self, start: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], size: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64], is_present: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool], name: str) -> IntervalVar:
1884    def new_optional_fixed_size_interval_var(
1885        self,
1886        start: LinearExprT,
1887        size: IntegralT,
1888        is_present: LiteralT,
1889        name: str,
1890    ) -> IntervalVar:
1891        """Creates an interval variable from start, and a fixed size.
1892
1893        An interval variable is a constraint, that is itself used in other
1894        constraints like NoOverlap.
1895
1896        Args:
1897          start: The start of the interval. It must be of the form a * var + b.
1898          size: The size of the interval. It must be an integer value.
1899          is_present: A literal that indicates if the interval is active or not. A
1900            inactive interval is simply ignored by all constraints.
1901          name: The name of the interval variable.
1902
1903        Returns:
1904          An `IntervalVar` object.
1905        """
1906        start_expr = self.parse_linear_expression(start)
1907        size_expr = self.parse_linear_expression(size)
1908        end_expr = self.parse_linear_expression(start + size)
1909        if len(start_expr.vars) > 1:
1910            raise TypeError(
1911                "cp_model.new_interval_var: start must be affine or constant."
1912            )
1913        is_present_index = self.get_or_make_boolean_index(is_present)
1914        return IntervalVar(
1915            self.__model,
1916            self.__var_list,
1917            start_expr,
1918            size_expr,
1919            end_expr,
1920            is_present_index,
1921            name,
1922        )

Creates an interval variable from start, and a fixed size.

An interval variable is a constraint, that is itself used in other constraints like NoOverlap.

Arguments:
  • start: The start of the interval. It must be of the form a * var + b.
  • size: The size of the interval. It must be an integer value.
  • is_present: A literal that indicates if the interval is active or not. A inactive interval is simply ignored by all constraints.
  • name: The name of the interval variable.
Returns:

An IntervalVar object.

def NewOptionalFixedSizeIntervalVarSeries( self, name: str, index: pandas.core.indexes.base.Index, starts: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], sizes: Union[int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, pandas.core.series.Series], are_present: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool, pandas.core.series.Series]) -> pandas.core.series.Series:
1924    def new_optional_fixed_size_interval_var_series(
1925        self,
1926        name: str,
1927        index: pd.Index,
1928        starts: Union[LinearExprT, pd.Series],
1929        sizes: Union[IntegralT, pd.Series],
1930        are_present: Union[LiteralT, pd.Series],
1931    ) -> pd.Series:
1932        """Creates a series of interval variables with the given name.
1933
1934        Args:
1935          name (str): Required. The name of the variable set.
1936          index (pd.Index): Required. The index to use for the variable set.
1937          starts (Union[LinearExprT, pd.Series]): The start of each interval in the
1938            set. If a `pd.Series` is passed in, it will be based on the
1939            corresponding values of the pd.Series.
1940          sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in
1941            the set. If a `pd.Series` is passed in, it will be based on the
1942            corresponding values of the pd.Series.
1943          are_present (Union[LiteralT, pd.Series]): The performed literal of each
1944            interval in the set. If a `pd.Series` is passed in, it will be based on
1945            the corresponding values of the pd.Series.
1946
1947        Returns:
1948          pd.Series: The interval variable set indexed by its corresponding
1949          dimensions.
1950
1951        Raises:
1952          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1953          ValueError: if the `name` is not a valid identifier or already exists.
1954          ValueError: if the all the indexes do not match.
1955        """
1956        if not isinstance(index, pd.Index):
1957            raise TypeError("Non-index object is used as index")
1958        if not name.isidentifier():
1959            raise ValueError(f"name={name!r} is not a valid identifier")
1960
1961        starts = _convert_to_linear_expr_series_and_validate_index(starts, index)
1962        sizes = _convert_to_integral_series_and_validate_index(sizes, index)
1963        are_present = _convert_to_literal_series_and_validate_index(are_present, index)
1964        interval_array = []
1965        for i in index:
1966            interval_array.append(
1967                self.new_optional_fixed_size_interval_var(
1968                    start=starts[i],
1969                    size=sizes[i],
1970                    is_present=are_present[i],
1971                    name=f"{name}[{i}]",
1972                )
1973            )
1974        return pd.Series(index=index, data=interval_array)

Creates a series of interval variables with the given name.

Arguments:
  • name (str): Required. The name of the variable set.
  • index (pd.Index): Required. The index to use for the variable set.
  • starts (Union[LinearExprT, pd.Series]): The start of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
  • are_present (Union[LiteralT, pd.Series]): The performed literal of each interval in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series.
Returns:

pd.Series: The interval variable set indexed by its corresponding dimensions.

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the all the indexes do not match.
def AddNoOverlap( self, interval_vars: Iterable[IntervalVar]) -> Constraint:
1976    def add_no_overlap(self, interval_vars: Iterable[IntervalVar]) -> Constraint:
1977        """Adds NoOverlap(interval_vars).
1978
1979        A NoOverlap constraint ensures that all present intervals do not overlap
1980        in time.
1981
1982        Args:
1983          interval_vars: The list of interval variables to constrain.
1984
1985        Returns:
1986          An instance of the `Constraint` class.
1987        """
1988        ct = Constraint(self)
1989        model_ct = self.__model.constraints[ct.index]
1990        model_ct.no_overlap.intervals.extend(
1991            [self.get_interval_index(x) for x in interval_vars]
1992        )
1993        return ct

Adds NoOverlap(interval_vars).

A NoOverlap constraint ensures that all present intervals do not overlap in time.

Arguments:
  • interval_vars: The list of interval variables to constrain.
Returns:

An instance of the Constraint class.

def AddNoOverlap2D( self, x_intervals: Iterable[IntervalVar], y_intervals: Iterable[IntervalVar]) -> Constraint:
1995    def add_no_overlap_2d(
1996        self,
1997        x_intervals: Iterable[IntervalVar],
1998        y_intervals: Iterable[IntervalVar],
1999    ) -> Constraint:
2000        """Adds NoOverlap2D(x_intervals, y_intervals).
2001
2002        A NoOverlap2D constraint ensures that all present rectangles do not overlap
2003        on a plane. Each rectangle is aligned with the X and Y axis, and is defined
2004        by two intervals which represent its projection onto the X and Y axis.
2005
2006        Furthermore, one box is optional if at least one of the x or y interval is
2007        optional.
2008
2009        Args:
2010          x_intervals: The X coordinates of the rectangles.
2011          y_intervals: The Y coordinates of the rectangles.
2012
2013        Returns:
2014          An instance of the `Constraint` class.
2015        """
2016        ct = Constraint(self)
2017        model_ct = self.__model.constraints[ct.index]
2018        model_ct.no_overlap_2d.x_intervals.extend(
2019            [self.get_interval_index(x) for x in x_intervals]
2020        )
2021        model_ct.no_overlap_2d.y_intervals.extend(
2022            [self.get_interval_index(x) for x in y_intervals]
2023        )
2024        return ct

Adds NoOverlap2D(x_intervals, y_intervals).

A NoOverlap2D constraint ensures that all present rectangles do not overlap on a plane. Each rectangle is aligned with the X and Y axis, and is defined by two intervals which represent its projection onto the X and Y axis.

Furthermore, one box is optional if at least one of the x or y interval is optional.

Arguments:
  • x_intervals: The X coordinates of the rectangles.
  • y_intervals: The Y coordinates of the rectangles.
Returns:

An instance of the Constraint class.

def AddCumulative( self, intervals: Iterable[IntervalVar], demands: Iterable[Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]], capacity: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> Constraint:
2026    def add_cumulative(
2027        self,
2028        intervals: Iterable[IntervalVar],
2029        demands: Iterable[LinearExprT],
2030        capacity: LinearExprT,
2031    ) -> Constraint:
2032        """Adds Cumulative(intervals, demands, capacity).
2033
2034        This constraint enforces that:
2035
2036            for all t:
2037              sum(demands[i]
2038                if (start(intervals[i]) <= t < end(intervals[i])) and
2039                (intervals[i] is present)) <= capacity
2040
2041        Args:
2042          intervals: The list of intervals.
2043          demands: The list of demands for each interval. Each demand must be >= 0.
2044            Each demand can be a 1-var affine expression (a * x + b).
2045          capacity: The maximum capacity of the cumulative constraint. It can be a
2046            1-var affine expression (a * x + b).
2047
2048        Returns:
2049          An instance of the `Constraint` class.
2050        """
2051        cumulative = Constraint(self)
2052        model_ct = self.__model.constraints[cumulative.index]
2053        model_ct.cumulative.intervals.extend(
2054            [self.get_interval_index(x) for x in intervals]
2055        )
2056        for d in demands:
2057            model_ct.cumulative.demands.append(self.parse_linear_expression(d))
2058        model_ct.cumulative.capacity.CopyFrom(self.parse_linear_expression(capacity))
2059        return cumulative

Adds Cumulative(intervals, demands, capacity).

This constraint enforces that:

for all t: sum(demands[i] if (start(intervals[i]) <= t < end(intervals[i])) and (intervals[i] is present)) <= capacity

Arguments:
  • intervals: The list of intervals.
  • demands: The list of demands for each interval. Each demand must be >= 0. Each demand can be a 1-var affine expression (a * x + b).
  • capacity: The maximum capacity of the cumulative constraint. It can be a 1-var affine expression (a * x + b).
Returns:

An instance of the Constraint class.

def Clone(self) -> CpModel:
2062    def clone(self) -> "CpModel":
2063        """Reset the model, and creates a new one from a CpModelProto instance."""
2064        clone = CpModel()
2065        clone.proto.CopyFrom(self.proto)
2066        clone.rebuild_var_and_constant_map()
2067        return clone

Reset the model, and creates a new one from a CpModelProto instance.

def GetBoolVarFromProtoIndex(self, index: int) -> IntVar:
2085    def get_bool_var_from_proto_index(self, index: int) -> IntVar:
2086        """Returns an already created Boolean variable from its index."""
2087        result = self._get_int_var(index)
2088        if not result.is_boolean:
2089            raise ValueError(
2090                f"get_bool_var_from_proto_index: index {index} does not reference a"
2091                " boolean variable"
2092            )
2093        return result

Returns an already created Boolean variable from its index.

def GetIntVarFromProtoIndex(self, index: int) -> IntVar:
2095    def get_int_var_from_proto_index(self, index: int) -> IntVar:
2096        """Returns an already created integer variable from its index."""
2097        return self._get_int_var(index)

Returns an already created integer variable from its index.

def GetIntervalVarFromProtoIndex(self, index: int) -> IntervalVar:
2099    def get_interval_var_from_proto_index(self, index: int) -> IntervalVar:
2100        """Returns an already created interval variable from its index."""
2101        if index < 0 or index >= len(self.__model.constraints):
2102            raise ValueError(
2103                f"get_interval_var_from_proto_index: out of bound index {index}"
2104            )
2105        ct = self.__model.constraints[index]
2106        if not ct.HasField("interval"):
2107            raise ValueError(
2108                f"get_interval_var_from_proto_index: index {index} does not"
2109                " reference an" + " interval variable"
2110            )
2111
2112        return IntervalVar(self.__model, self.__var_list, index, None, None, None, None)

Returns an already created interval variable from its index.

def Minimize( self, obj: Union[LinearExpr, int, float, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, numpy.float64]):
2225    def minimize(self, obj: ObjLinearExprT):
2226        """Sets the objective of the model to minimize(obj)."""
2227        self._set_objective(obj, minimize=True)

Sets the objective of the model to minimize(obj).

def Maximize( self, obj: Union[LinearExpr, int, float, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, numpy.float64]):
2229    def maximize(self, obj: ObjLinearExprT):
2230        """Sets the objective of the model to maximize(obj)."""
2231        self._set_objective(obj, minimize=False)

Sets the objective of the model to maximize(obj).

def HasObjective(self) -> bool:
2233    def has_objective(self) -> bool:
2234        return self.__model.HasField("objective") or self.__model.HasField(
2235            "floating_point_objective"
2236        )
def ClearObjective(self):
2238    def clear_objective(self):
2239        self.__model.ClearField("objective")
2240        self.__model.ClearField("floating_point_objective")
def AddDecisionStrategy( self, variables: Sequence[IntVar], var_strategy: <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object>, domain_strategy: <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object>) -> None:
2242    def add_decision_strategy(
2243        self,
2244        variables: Sequence[IntVar],
2245        var_strategy: cp_model_pb2.DecisionStrategyProto.VariableSelectionStrategy,
2246        domain_strategy: cp_model_pb2.DecisionStrategyProto.DomainReductionStrategy,
2247    ) -> None:
2248        """Adds a search strategy to the model.
2249
2250        Args:
2251          variables: a list of variables this strategy will assign.
2252          var_strategy: heuristic to choose the next variable to assign.
2253          domain_strategy: heuristic to reduce the domain of the selected variable.
2254            Currently, this is advanced code: the union of all strategies added to
2255            the model must be complete, i.e. instantiates all variables. Otherwise,
2256            solve() will fail.
2257        """
2258
2259        strategy: cp_model_pb2.DecisionStrategyProto = (
2260            self.__model.search_strategy.add()
2261        )
2262        for v in variables:
2263            expr = strategy.exprs.add()
2264            if v.index >= 0:
2265                expr.vars.append(v.index)
2266                expr.coeffs.append(1)
2267            else:
2268                expr.vars.append(self.negated(v.index))
2269                expr.coeffs.append(-1)
2270                expr.offset = 1
2271
2272        strategy.variable_selection_strategy = var_strategy
2273        strategy.domain_reduction_strategy = domain_strategy

Adds a search strategy to the model.

Arguments:
  • variables: a list of variables this strategy will assign.
  • var_strategy: heuristic to choose the next variable to assign.
  • domain_strategy: heuristic to reduce the domain of the selected variable. Currently, this is advanced code: the union of all strategies added to the model must be complete, i.e. instantiates all variables. Otherwise, solve() will fail.
def ModelStats(self) -> str:
2275    def model_stats(self) -> str:
2276        """Returns a string containing some model statistics."""
2277        return cmh.CpSatHelper.model_stats(self.__model)

Returns a string containing some model statistics.

def Validate(self) -> str:
2279    def validate(self) -> str:
2280        """Returns a string indicating that the model is invalid."""
2281        return cmh.CpSatHelper.validate_model(self.__model)

Returns a string indicating that the model is invalid.

def ExportToFile(self, file: str) -> bool:
2283    def export_to_file(self, file: str) -> bool:
2284        """Write the model as a protocol buffer to 'file'.
2285
2286        Args:
2287          file: file to write the model to. If the filename ends with 'txt', the
2288            model will be written as a text file, otherwise, the binary format will
2289            be used.
2290
2291        Returns:
2292          True if the model was correctly written.
2293        """
2294        return cmh.CpSatHelper.write_model_to_file(self.__model, file)

Write the model as a protocol buffer to 'file'.

Arguments:
  • file: file to write the model to. If the filename ends with 'txt', the model will be written as a text file, otherwise, the binary format will be used.
Returns:

True if the model was correctly written.

def AddHint(self, var, value) -> None:
2302    def add_hint(self, var, value) -> None:
2303        """Adds 'var == value' as a hint to the solver."""
2304        if var.index >= 0:
2305            self.__model.solution_hint.vars.append(self.get_or_make_index(var))
2306            self.__model.solution_hint.values.append(int(value))
2307        else:
2308            self.__model.solution_hint.vars.append(self.negated(var.index))
2309            self.__model.solution_hint.values.append(int(not value))

Adds 'var == value' as a hint to the solver.

def ClearHints(self):
2311    def clear_hints(self):
2312        """Removes any solution hint from the model."""
2313        self.__model.ClearField("solution_hint")

Removes any solution hint from the model.

def AddAssumption( self, lit: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> None:
2315    def add_assumption(self, lit: LiteralT) -> None:
2316        """Adds the literal to the model as assumptions."""
2317        self.__model.assumptions.append(self.get_or_make_boolean_index(lit))

Adds the literal to the model as assumptions.

def AddAssumptions( self, literals: Iterable[Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]]) -> None:
2319    def add_assumptions(self, literals: Iterable[LiteralT]) -> None:
2320        """Adds the literals to the model as assumptions."""
2321        for lit in literals:
2322            self.add_assumption(lit)

Adds the literals to the model as assumptions.

def ClearAssumptions(self) -> None:
2324    def clear_assumptions(self) -> None:
2325        """Removes all assumptions from the model."""
2326        self.__model.ClearField("assumptions")

Removes all assumptions from the model.

def expand_generator_or_tuple(args):
2428def expand_generator_or_tuple(args):
2429    if hasattr(args, "__len__"):  # Tuple
2430        if len(args) != 1:
2431            return args
2432        if isinstance(args[0], (NumberTypes, LinearExpr)):
2433            return args
2434    # Generator
2435    return args[0]
class CpSolver:
2438class CpSolver:
2439    """Main solver class.
2440
2441    The purpose of this class is to search for a solution to the model provided
2442    to the solve() method.
2443
2444    Once solve() is called, this class allows inspecting the solution found
2445    with the value() and boolean_value() methods, as well as general statistics
2446    about the solve procedure.
2447    """
2448
2449    def __init__(self) -> None:
2450        self.__response_wrapper: Optional[cmh.ResponseWrapper] = None
2451        self.parameters: sat_parameters_pb2.SatParameters = (
2452            sat_parameters_pb2.SatParameters()
2453        )
2454        self.log_callback: Optional[Callable[[str], None]] = None
2455        self.best_bound_callback: Optional[Callable[[float], None]] = None
2456        self.__solve_wrapper: Optional[cmh.SolveWrapper] = None
2457        self.__lock: threading.Lock = threading.Lock()
2458
2459    def solve(
2460        self,
2461        model: CpModel,
2462        solution_callback: Optional["CpSolverSolutionCallback"] = None,
2463    ) -> cp_model_pb2.CpSolverStatus:
2464        """Solves a problem and passes each solution to the callback if not null."""
2465        with self.__lock:
2466            self.__solve_wrapper = cmh.SolveWrapper()
2467
2468        self.__solve_wrapper.set_parameters(self.parameters)
2469        if solution_callback is not None:
2470            self.__solve_wrapper.add_solution_callback(solution_callback)
2471
2472        if self.log_callback is not None:
2473            self.__solve_wrapper.add_log_callback(self.log_callback)
2474
2475        if self.best_bound_callback is not None:
2476            self.__solve_wrapper.add_best_bound_callback(self.best_bound_callback)
2477
2478        self.__response_wrapper = (
2479            self.__solve_wrapper.solve_and_return_response_wrapper(model.proto)
2480        )
2481
2482        if solution_callback is not None:
2483            self.__solve_wrapper.clear_solution_callback(solution_callback)
2484
2485        with self.__lock:
2486            self.__solve_wrapper = None
2487
2488        return self.__response_wrapper.status()
2489
2490    def stop_search(self) -> None:
2491        """Stops the current search asynchronously."""
2492        with self.__lock:
2493            if self.__solve_wrapper:
2494                self.__solve_wrapper.stop_search()
2495
2496    def value(self, expression: LinearExprT) -> int:
2497        """Returns the value of a linear expression after solve."""
2498        return self._checked_response.value(expression)
2499
2500    def values(self, variables: _IndexOrSeries) -> pd.Series:
2501        """Returns the values of the input variables.
2502
2503        If `variables` is a `pd.Index`, then the output will be indexed by the
2504        variables. If `variables` is a `pd.Series` indexed by the underlying
2505        dimensions, then the output will be indexed by the same underlying
2506        dimensions.
2507
2508        Args:
2509          variables (Union[pd.Index, pd.Series]): The set of variables from which to
2510            get the values.
2511
2512        Returns:
2513          pd.Series: The values of all variables in the set.
2514
2515        Raises:
2516          RuntimeError: if solve() has not been called.
2517        """
2518        if self.__response_wrapper is None:
2519            raise RuntimeError("solve() has not been called.")
2520        return pd.Series(
2521            data=[self.__response_wrapper.value(var) for var in variables],
2522            index=_get_index(variables),
2523        )
2524
2525    def boolean_value(self, literal: LiteralT) -> bool:
2526        """Returns the boolean value of a literal after solve."""
2527        return self._checked_response.boolean_value(literal)
2528
2529    def boolean_values(self, variables: _IndexOrSeries) -> pd.Series:
2530        """Returns the values of the input variables.
2531
2532        If `variables` is a `pd.Index`, then the output will be indexed by the
2533        variables. If `variables` is a `pd.Series` indexed by the underlying
2534        dimensions, then the output will be indexed by the same underlying
2535        dimensions.
2536
2537        Args:
2538          variables (Union[pd.Index, pd.Series]): The set of variables from which to
2539            get the values.
2540
2541        Returns:
2542          pd.Series: The values of all variables in the set.
2543
2544        Raises:
2545          RuntimeError: if solve() has not been called.
2546        """
2547        if self.__response_wrapper is None:
2548            raise RuntimeError("solve() has not been called.")
2549        return pd.Series(
2550            data=[
2551                self.__response_wrapper.boolean_value(literal) for literal in variables
2552            ],
2553            index=_get_index(variables),
2554        )
2555
2556    @property
2557    def objective_value(self) -> float:
2558        """Returns the value of the objective after solve."""
2559        return self._checked_response.objective_value()
2560
2561    @property
2562    def best_objective_bound(self) -> float:
2563        """Returns the best lower (upper) bound found when min(max)imizing."""
2564        return self._checked_response.best_objective_bound()
2565
2566    @property
2567    def num_booleans(self) -> int:
2568        """Returns the number of boolean variables managed by the SAT solver."""
2569        return self._checked_response.num_booleans()
2570
2571    @property
2572    def num_conflicts(self) -> int:
2573        """Returns the number of conflicts since the creation of the solver."""
2574        return self._checked_response.num_conflicts()
2575
2576    @property
2577    def num_branches(self) -> int:
2578        """Returns the number of search branches explored by the solver."""
2579        return self._checked_response.num_branches()
2580
2581    @property
2582    def wall_time(self) -> float:
2583        """Returns the wall time in seconds since the creation of the solver."""
2584        return self._checked_response.wall_time()
2585
2586    @property
2587    def user_time(self) -> float:
2588        """Returns the user time in seconds since the creation of the solver."""
2589        return self._checked_response.user_time()
2590
2591    @property
2592    def response_proto(self) -> cp_model_pb2.CpSolverResponse:
2593        """Returns the response object."""
2594        return self._checked_response.response()
2595
2596    def response_stats(self) -> str:
2597        """Returns some statistics on the solution found as a string."""
2598        return self._checked_response.response_stats()
2599
2600    def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]:
2601        """Returns the indices of the infeasible assumptions."""
2602        return self._checked_response.sufficient_assumptions_for_infeasibility()
2603
2604    def status_name(self, status: Optional[Any] = None) -> str:
2605        """Returns the name of the status returned by solve()."""
2606        if status is None:
2607            status = self._checked_response.status()
2608        return cp_model_pb2.CpSolverStatus.Name(status)
2609
2610    def solution_info(self) -> str:
2611        """Returns some information on the solve process.
2612
2613        Returns some information on how the solution was found, or the reason
2614        why the model or the parameters are invalid.
2615
2616        Raises:
2617          RuntimeError: if solve() has not been called.
2618        """
2619        return self._checked_response.solution_info()
2620
2621    @property
2622    def _checked_response(self) -> cmh.ResponseWrapper:
2623        """Checks solve() has been called, and returns a response wrapper."""
2624        if self.__response_wrapper is None:
2625            raise RuntimeError("solve() has not been called.")
2626        return self.__response_wrapper
2627
2628    # Compatibility with pre PEP8
2629    # pylint: disable=invalid-name
2630
2631    def BestObjectiveBound(self) -> float:
2632        return self.best_objective_bound
2633
2634    def BooleanValue(self, literal: LiteralT) -> bool:
2635        return self.boolean_value(literal)
2636
2637    def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series:
2638        return self.boolean_values(variables)
2639
2640    def NumBooleans(self) -> int:
2641        return self.num_booleans
2642
2643    def NumConflicts(self) -> int:
2644        return self.num_conflicts
2645
2646    def NumBranches(self) -> int:
2647        return self.num_branches
2648
2649    def ObjectiveValue(self) -> float:
2650        return self.objective_value
2651
2652    def ResponseProto(self) -> cp_model_pb2.CpSolverResponse:
2653        return self.response_proto
2654
2655    def ResponseStats(self) -> str:
2656        return self.response_stats()
2657
2658    def Solve(
2659        self,
2660        model: CpModel,
2661        solution_callback: Optional["CpSolverSolutionCallback"] = None,
2662    ) -> cp_model_pb2.CpSolverStatus:
2663        return self.solve(model, solution_callback)
2664
2665    def SolutionInfo(self) -> str:
2666        return self.solution_info()
2667
2668    def StatusName(self, status: Optional[Any] = None) -> str:
2669        return self.status_name(status)
2670
2671    def StopSearch(self) -> None:
2672        self.stop_search()
2673
2674    def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]:
2675        return self.sufficient_assumptions_for_infeasibility()
2676
2677    def UserTime(self) -> float:
2678        return self.user_time
2679
2680    def Value(self, expression: LinearExprT) -> int:
2681        return self.value(expression)
2682
2683    def Values(self, variables: _IndexOrSeries) -> pd.Series:
2684        return self.values(variables)
2685
2686    def WallTime(self) -> float:
2687        return self.wall_time
2688
2689    def SolveWithSolutionCallback(
2690        self, model: CpModel, callback: "CpSolverSolutionCallback"
2691    ) -> cp_model_pb2.CpSolverStatus:
2692        """DEPRECATED Use solve() with the callback argument."""
2693        warnings.warn(
2694            "solve_with_solution_callback is deprecated; use solve() with"
2695            + "the callback argument.",
2696            DeprecationWarning,
2697        )
2698        return self.solve(model, callback)
2699
2700    def SearchForAllSolutions(
2701        self, model: CpModel, callback: "CpSolverSolutionCallback"
2702    ) -> cp_model_pb2.CpSolverStatus:
2703        """DEPRECATED Use solve() with the right parameter.
2704
2705        Search for all solutions of a satisfiability problem.
2706
2707        This method searches for all feasible solutions of a given model.
2708        Then it feeds the solution to the callback.
2709
2710        Note that the model cannot contain an objective.
2711
2712        Args:
2713          model: The model to solve.
2714          callback: The callback that will be called at each solution.
2715
2716        Returns:
2717          The status of the solve:
2718
2719          * *FEASIBLE* if some solutions have been found
2720          * *INFEASIBLE* if the solver has proved there are no solution
2721          * *OPTIMAL* if all solutions have been found
2722        """
2723        warnings.warn(
2724            "search_for_all_solutions is deprecated; use solve() with"
2725            + "enumerate_all_solutions = True.",
2726            DeprecationWarning,
2727        )
2728        if model.has_objective():
2729            raise TypeError(
2730                "Search for all solutions is only defined on satisfiability problems"
2731            )
2732        # Store old parameter.
2733        enumerate_all = self.parameters.enumerate_all_solutions
2734        self.parameters.enumerate_all_solutions = True
2735
2736        status: cp_model_pb2.CpSolverStatus = self.solve(model, callback)
2737
2738        # Restore parameter.
2739        self.parameters.enumerate_all_solutions = enumerate_all
2740        return status

Main solver class.

The purpose of this class is to search for a solution to the model provided to the solve() method.

Once solve() is called, this class allows inspecting the solution found with the value() and boolean_value() methods, as well as general statistics about the solve procedure.

log_callback: Optional[Callable[[str], NoneType]]
best_bound_callback: Optional[Callable[[float], NoneType]]
def solve( self, model: CpModel, solution_callback: Optional[CpSolverSolutionCallback] = None) -> <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object at 0x7fb8001061b0>:
2459    def solve(
2460        self,
2461        model: CpModel,
2462        solution_callback: Optional["CpSolverSolutionCallback"] = None,
2463    ) -> cp_model_pb2.CpSolverStatus:
2464        """Solves a problem and passes each solution to the callback if not null."""
2465        with self.__lock:
2466            self.__solve_wrapper = cmh.SolveWrapper()
2467
2468        self.__solve_wrapper.set_parameters(self.parameters)
2469        if solution_callback is not None:
2470            self.__solve_wrapper.add_solution_callback(solution_callback)
2471
2472        if self.log_callback is not None:
2473            self.__solve_wrapper.add_log_callback(self.log_callback)
2474
2475        if self.best_bound_callback is not None:
2476            self.__solve_wrapper.add_best_bound_callback(self.best_bound_callback)
2477
2478        self.__response_wrapper = (
2479            self.__solve_wrapper.solve_and_return_response_wrapper(model.proto)
2480        )
2481
2482        if solution_callback is not None:
2483            self.__solve_wrapper.clear_solution_callback(solution_callback)
2484
2485        with self.__lock:
2486            self.__solve_wrapper = None
2487
2488        return self.__response_wrapper.status()

Solves a problem and passes each solution to the callback if not null.

def value( self, expression: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> int:
2496    def value(self, expression: LinearExprT) -> int:
2497        """Returns the value of a linear expression after solve."""
2498        return self._checked_response.value(expression)

Returns the value of a linear expression after solve.

def values( self, variables: Union[pandas.core.indexes.base.Index, pandas.core.series.Series]) -> pandas.core.series.Series:
2500    def values(self, variables: _IndexOrSeries) -> pd.Series:
2501        """Returns the values of the input variables.
2502
2503        If `variables` is a `pd.Index`, then the output will be indexed by the
2504        variables. If `variables` is a `pd.Series` indexed by the underlying
2505        dimensions, then the output will be indexed by the same underlying
2506        dimensions.
2507
2508        Args:
2509          variables (Union[pd.Index, pd.Series]): The set of variables from which to
2510            get the values.
2511
2512        Returns:
2513          pd.Series: The values of all variables in the set.
2514
2515        Raises:
2516          RuntimeError: if solve() has not been called.
2517        """
2518        if self.__response_wrapper is None:
2519            raise RuntimeError("solve() has not been called.")
2520        return pd.Series(
2521            data=[self.__response_wrapper.value(var) for var in variables],
2522            index=_get_index(variables),
2523        )

Returns the values of the input variables.

If variables is a pd.Index, then the output will be indexed by the variables. If variables is a pd.Series indexed by the underlying dimensions, then the output will be indexed by the same underlying dimensions.

Arguments:
  • variables (Union[pd.Index, pd.Series]): The set of variables from which to get the values.
Returns:

pd.Series: The values of all variables in the set.

Raises:
  • RuntimeError: if solve() has not been called.
def boolean_value( self, literal: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> bool:
2525    def boolean_value(self, literal: LiteralT) -> bool:
2526        """Returns the boolean value of a literal after solve."""
2527        return self._checked_response.boolean_value(literal)

Returns the boolean value of a literal after solve.

def boolean_values( self, variables: Union[pandas.core.indexes.base.Index, pandas.core.series.Series]) -> pandas.core.series.Series:
2529    def boolean_values(self, variables: _IndexOrSeries) -> pd.Series:
2530        """Returns the values of the input variables.
2531
2532        If `variables` is a `pd.Index`, then the output will be indexed by the
2533        variables. If `variables` is a `pd.Series` indexed by the underlying
2534        dimensions, then the output will be indexed by the same underlying
2535        dimensions.
2536
2537        Args:
2538          variables (Union[pd.Index, pd.Series]): The set of variables from which to
2539            get the values.
2540
2541        Returns:
2542          pd.Series: The values of all variables in the set.
2543
2544        Raises:
2545          RuntimeError: if solve() has not been called.
2546        """
2547        if self.__response_wrapper is None:
2548            raise RuntimeError("solve() has not been called.")
2549        return pd.Series(
2550            data=[
2551                self.__response_wrapper.boolean_value(literal) for literal in variables
2552            ],
2553            index=_get_index(variables),
2554        )

Returns the values of the input variables.

If variables is a pd.Index, then the output will be indexed by the variables. If variables is a pd.Series indexed by the underlying dimensions, then the output will be indexed by the same underlying dimensions.

Arguments:
  • variables (Union[pd.Index, pd.Series]): The set of variables from which to get the values.
Returns:

pd.Series: The values of all variables in the set.

Raises:
  • RuntimeError: if solve() has not been called.
objective_value: float
2556    @property
2557    def objective_value(self) -> float:
2558        """Returns the value of the objective after solve."""
2559        return self._checked_response.objective_value()

Returns the value of the objective after solve.

best_objective_bound: float
2561    @property
2562    def best_objective_bound(self) -> float:
2563        """Returns the best lower (upper) bound found when min(max)imizing."""
2564        return self._checked_response.best_objective_bound()

Returns the best lower (upper) bound found when min(max)imizing.

num_booleans: int
2566    @property
2567    def num_booleans(self) -> int:
2568        """Returns the number of boolean variables managed by the SAT solver."""
2569        return self._checked_response.num_booleans()

Returns the number of boolean variables managed by the SAT solver.

num_conflicts: int
2571    @property
2572    def num_conflicts(self) -> int:
2573        """Returns the number of conflicts since the creation of the solver."""
2574        return self._checked_response.num_conflicts()

Returns the number of conflicts since the creation of the solver.

num_branches: int
2576    @property
2577    def num_branches(self) -> int:
2578        """Returns the number of search branches explored by the solver."""
2579        return self._checked_response.num_branches()

Returns the number of search branches explored by the solver.

wall_time: float
2581    @property
2582    def wall_time(self) -> float:
2583        """Returns the wall time in seconds since the creation of the solver."""
2584        return self._checked_response.wall_time()

Returns the wall time in seconds since the creation of the solver.

user_time: float
2586    @property
2587    def user_time(self) -> float:
2588        """Returns the user time in seconds since the creation of the solver."""
2589        return self._checked_response.user_time()

Returns the user time in seconds since the creation of the solver.

response_proto: ortools.sat.cp_model_pb2.CpSolverResponse
2591    @property
2592    def response_proto(self) -> cp_model_pb2.CpSolverResponse:
2593        """Returns the response object."""
2594        return self._checked_response.response()

Returns the response object.

def response_stats(self) -> str:
2596    def response_stats(self) -> str:
2597        """Returns some statistics on the solution found as a string."""
2598        return self._checked_response.response_stats()

Returns some statistics on the solution found as a string.

def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]:
2600    def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]:
2601        """Returns the indices of the infeasible assumptions."""
2602        return self._checked_response.sufficient_assumptions_for_infeasibility()

Returns the indices of the infeasible assumptions.

def status_name(self, status: Optional[Any] = None) -> str:
2604    def status_name(self, status: Optional[Any] = None) -> str:
2605        """Returns the name of the status returned by solve()."""
2606        if status is None:
2607            status = self._checked_response.status()
2608        return cp_model_pb2.CpSolverStatus.Name(status)

Returns the name of the status returned by solve().

def solution_info(self) -> str:
2610    def solution_info(self) -> str:
2611        """Returns some information on the solve process.
2612
2613        Returns some information on how the solution was found, or the reason
2614        why the model or the parameters are invalid.
2615
2616        Raises:
2617          RuntimeError: if solve() has not been called.
2618        """
2619        return self._checked_response.solution_info()

Returns some information on the solve process.

Returns some information on how the solution was found, or the reason why the model or the parameters are invalid.

Raises:
  • RuntimeError: if solve() has not been called.
def BestObjectiveBound(self) -> float:
2631    def BestObjectiveBound(self) -> float:
2632        return self.best_objective_bound
def BooleanValue( self, literal: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> bool:
2634    def BooleanValue(self, literal: LiteralT) -> bool:
2635        return self.boolean_value(literal)
def BooleanValues( self, variables: Union[pandas.core.indexes.base.Index, pandas.core.series.Series]) -> pandas.core.series.Series:
2637    def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series:
2638        return self.boolean_values(variables)
def NumBooleans(self) -> int:
2640    def NumBooleans(self) -> int:
2641        return self.num_booleans
def NumConflicts(self) -> int:
2643    def NumConflicts(self) -> int:
2644        return self.num_conflicts
def NumBranches(self) -> int:
2646    def NumBranches(self) -> int:
2647        return self.num_branches
def ObjectiveValue(self) -> float:
2649    def ObjectiveValue(self) -> float:
2650        return self.objective_value
def ResponseProto(self) -> ortools.sat.cp_model_pb2.CpSolverResponse:
2652    def ResponseProto(self) -> cp_model_pb2.CpSolverResponse:
2653        return self.response_proto
def ResponseStats(self) -> str:
2655    def ResponseStats(self) -> str:
2656        return self.response_stats()
def Solve( self, model: CpModel, solution_callback: Optional[CpSolverSolutionCallback] = None) -> <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object at 0x7fb8001061b0>:
2658    def Solve(
2659        self,
2660        model: CpModel,
2661        solution_callback: Optional["CpSolverSolutionCallback"] = None,
2662    ) -> cp_model_pb2.CpSolverStatus:
2663        return self.solve(model, solution_callback)
def SolutionInfo(self) -> str:
2665    def SolutionInfo(self) -> str:
2666        return self.solution_info()
def StatusName(self, status: Optional[Any] = None) -> str:
2668    def StatusName(self, status: Optional[Any] = None) -> str:
2669        return self.status_name(status)
def StopSearch(self) -> None:
2671    def StopSearch(self) -> None:
2672        self.stop_search()
def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]:
2674    def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]:
2675        return self.sufficient_assumptions_for_infeasibility()
def UserTime(self) -> float:
2677    def UserTime(self) -> float:
2678        return self.user_time
def Value( self, expression: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> int:
2680    def Value(self, expression: LinearExprT) -> int:
2681        return self.value(expression)
def Values( self, variables: Union[pandas.core.indexes.base.Index, pandas.core.series.Series]) -> pandas.core.series.Series:
2683    def Values(self, variables: _IndexOrSeries) -> pd.Series:
2684        return self.values(variables)
def WallTime(self) -> float:
2686    def WallTime(self) -> float:
2687        return self.wall_time
def SolveWithSolutionCallback( self, model: CpModel, callback: CpSolverSolutionCallback) -> <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object at 0x7fb8001061b0>:
2689    def SolveWithSolutionCallback(
2690        self, model: CpModel, callback: "CpSolverSolutionCallback"
2691    ) -> cp_model_pb2.CpSolverStatus:
2692        """DEPRECATED Use solve() with the callback argument."""
2693        warnings.warn(
2694            "solve_with_solution_callback is deprecated; use solve() with"
2695            + "the callback argument.",
2696            DeprecationWarning,
2697        )
2698        return self.solve(model, callback)

DEPRECATED Use solve() with the callback argument.

def SearchForAllSolutions( self, model: CpModel, callback: CpSolverSolutionCallback) -> <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object at 0x7fb8001061b0>:
2700    def SearchForAllSolutions(
2701        self, model: CpModel, callback: "CpSolverSolutionCallback"
2702    ) -> cp_model_pb2.CpSolverStatus:
2703        """DEPRECATED Use solve() with the right parameter.
2704
2705        Search for all solutions of a satisfiability problem.
2706
2707        This method searches for all feasible solutions of a given model.
2708        Then it feeds the solution to the callback.
2709
2710        Note that the model cannot contain an objective.
2711
2712        Args:
2713          model: The model to solve.
2714          callback: The callback that will be called at each solution.
2715
2716        Returns:
2717          The status of the solve:
2718
2719          * *FEASIBLE* if some solutions have been found
2720          * *INFEASIBLE* if the solver has proved there are no solution
2721          * *OPTIMAL* if all solutions have been found
2722        """
2723        warnings.warn(
2724            "search_for_all_solutions is deprecated; use solve() with"
2725            + "enumerate_all_solutions = True.",
2726            DeprecationWarning,
2727        )
2728        if model.has_objective():
2729            raise TypeError(
2730                "Search for all solutions is only defined on satisfiability problems"
2731            )
2732        # Store old parameter.
2733        enumerate_all = self.parameters.enumerate_all_solutions
2734        self.parameters.enumerate_all_solutions = True
2735
2736        status: cp_model_pb2.CpSolverStatus = self.solve(model, callback)
2737
2738        # Restore parameter.
2739        self.parameters.enumerate_all_solutions = enumerate_all
2740        return status

DEPRECATED Use solve() with the right parameter.

Search for all solutions of a satisfiability problem.

This method searches for all feasible solutions of a given model. Then it feeds the solution to the callback.

Note that the model cannot contain an objective.

Arguments:
  • model: The model to solve.
  • callback: The callback that will be called at each solution.
Returns:

The status of the solve:

  • FEASIBLE if some solutions have been found
  • INFEASIBLE if the solver has proved there are no solution
  • OPTIMAL if all solutions have been found
class CpSolverSolutionCallback(ortools.sat.python.cp_model_helper.SolutionCallback):
2746class CpSolverSolutionCallback(cmh.SolutionCallback):
2747    """Solution callback.
2748
2749    This class implements a callback that will be called at each new solution
2750    found during search.
2751
2752    The method on_solution_callback() will be called by the solver, and must be
2753    implemented. The current solution can be queried using the boolean_value()
2754    and value() methods.
2755
2756    These methods returns the same information as their counterpart in the
2757    `CpSolver` class.
2758    """
2759
2760    def __init__(self) -> None:
2761        cmh.SolutionCallback.__init__(self)
2762
2763    def OnSolutionCallback(self) -> None:
2764        """Proxy for the same method in snake case."""
2765        self.on_solution_callback()
2766
2767    def boolean_value(self, lit: LiteralT) -> bool:
2768        """Returns the boolean value of a boolean literal.
2769
2770        Args:
2771            lit: A boolean variable or its negation.
2772
2773        Returns:
2774            The Boolean value of the literal in the solution.
2775
2776        Raises:
2777            RuntimeError: if `lit` is not a boolean variable or its negation.
2778        """
2779        if not self.has_response():
2780            raise RuntimeError("solve() has not been called.")
2781        return self.BooleanValue(lit)
2782
2783    def value(self, expression: LinearExprT) -> int:
2784        """Evaluates an linear expression in the current solution.
2785
2786        Args:
2787            expression: a linear expression of the model.
2788
2789        Returns:
2790            An integer value equal to the evaluation of the linear expression
2791            against the current solution.
2792
2793        Raises:
2794            RuntimeError: if 'expression' is not a LinearExpr.
2795        """
2796        if not self.has_response():
2797            raise RuntimeError("solve() has not been called.")
2798        return self.Value(expression)
2799
2800    def has_response(self) -> bool:
2801        return self.HasResponse()
2802
2803    def stop_search(self) -> None:
2804        """Stops the current search asynchronously."""
2805        if not self.has_response():
2806            raise RuntimeError("solve() has not been called.")
2807        self.StopSearch()
2808
2809    @property
2810    def objective_value(self) -> float:
2811        """Returns the value of the objective after solve."""
2812        if not self.has_response():
2813            raise RuntimeError("solve() has not been called.")
2814        return self.ObjectiveValue()
2815
2816    @property
2817    def best_objective_bound(self) -> float:
2818        """Returns the best lower (upper) bound found when min(max)imizing."""
2819        if not self.has_response():
2820            raise RuntimeError("solve() has not been called.")
2821        return self.BestObjectiveBound()
2822
2823    @property
2824    def num_booleans(self) -> int:
2825        """Returns the number of boolean variables managed by the SAT solver."""
2826        if not self.has_response():
2827            raise RuntimeError("solve() has not been called.")
2828        return self.NumBooleans()
2829
2830    @property
2831    def num_conflicts(self) -> int:
2832        """Returns the number of conflicts since the creation of the solver."""
2833        if not self.has_response():
2834            raise RuntimeError("solve() has not been called.")
2835        return self.NumConflicts()
2836
2837    @property
2838    def num_branches(self) -> int:
2839        """Returns the number of search branches explored by the solver."""
2840        if not self.has_response():
2841            raise RuntimeError("solve() has not been called.")
2842        return self.NumBranches()
2843
2844    @property
2845    def num_integer_propagations(self) -> int:
2846        """Returns the number of integer propagations done by the solver."""
2847        if not self.has_response():
2848            raise RuntimeError("solve() has not been called.")
2849        return self.NumIntegerPropagations()
2850
2851    @property
2852    def num_boolean_propagations(self) -> int:
2853        """Returns the number of Boolean propagations done by the solver."""
2854        if not self.has_response():
2855            raise RuntimeError("solve() has not been called.")
2856        return self.NumBooleanPropagations()
2857
2858    @property
2859    def deterministic_time(self) -> float:
2860        """Returns the determistic time in seconds since the creation of the solver."""
2861        if not self.has_response():
2862            raise RuntimeError("solve() has not been called.")
2863        return self.DeterministicTime()
2864
2865    @property
2866    def wall_time(self) -> float:
2867        """Returns the wall time in seconds since the creation of the solver."""
2868        if not self.has_response():
2869            raise RuntimeError("solve() has not been called.")
2870        return self.WallTime()
2871
2872    @property
2873    def user_time(self) -> float:
2874        """Returns the user time in seconds since the creation of the solver."""
2875        if not self.has_response():
2876            raise RuntimeError("solve() has not been called.")
2877        return self.UserTime()
2878
2879    @property
2880    def response_proto(self) -> cp_model_pb2.CpSolverResponse:
2881        """Returns the response object."""
2882        if not self.has_response():
2883            raise RuntimeError("solve() has not been called.")
2884        return self.Response()

Solution callback.

This class implements a callback that will be called at each new solution found during search.

The method on_solution_callback() will be called by the solver, and must be implemented. The current solution can be queried using the boolean_value() and value() methods.

These methods returns the same information as their counterpart in the CpSolver class.

CpSolverSolutionCallback()
2760    def __init__(self) -> None:
2761        cmh.SolutionCallback.__init__(self)
def OnSolutionCallback(self) -> None:
2763    def OnSolutionCallback(self) -> None:
2764        """Proxy for the same method in snake case."""
2765        self.on_solution_callback()

Proxy for the same method in snake case.

def boolean_value( self, lit: Union[ortools.sat.python.cp_model_helper.Literal, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64, bool]) -> bool:
2767    def boolean_value(self, lit: LiteralT) -> bool:
2768        """Returns the boolean value of a boolean literal.
2769
2770        Args:
2771            lit: A boolean variable or its negation.
2772
2773        Returns:
2774            The Boolean value of the literal in the solution.
2775
2776        Raises:
2777            RuntimeError: if `lit` is not a boolean variable or its negation.
2778        """
2779        if not self.has_response():
2780            raise RuntimeError("solve() has not been called.")
2781        return self.BooleanValue(lit)

Returns the boolean value of a boolean literal.

Arguments:
  • lit: A boolean variable or its negation.
Returns:

The Boolean value of the literal in the solution.

Raises:
  • RuntimeError: if lit is not a boolean variable or its negation.
def value( self, expression: Union[LinearExpr, IntVar, int, numpy.int8, numpy.uint8, numpy.int32, numpy.uint32, numpy.int64, numpy.uint64]) -> int:
2783    def value(self, expression: LinearExprT) -> int:
2784        """Evaluates an linear expression in the current solution.
2785
2786        Args:
2787            expression: a linear expression of the model.
2788
2789        Returns:
2790            An integer value equal to the evaluation of the linear expression
2791            against the current solution.
2792
2793        Raises:
2794            RuntimeError: if 'expression' is not a LinearExpr.
2795        """
2796        if not self.has_response():
2797            raise RuntimeError("solve() has not been called.")
2798        return self.Value(expression)

Evaluates an linear expression in the current solution.

Arguments:
  • expression: a linear expression of the model.
Returns:

An integer value equal to the evaluation of the linear expression against the current solution.

Raises:
  • RuntimeError: if 'expression' is not a LinearExpr.
def has_response(self) -> bool:
2800    def has_response(self) -> bool:
2801        return self.HasResponse()
objective_value: float
2809    @property
2810    def objective_value(self) -> float:
2811        """Returns the value of the objective after solve."""
2812        if not self.has_response():
2813            raise RuntimeError("solve() has not been called.")
2814        return self.ObjectiveValue()

Returns the value of the objective after solve.

best_objective_bound: float
2816    @property
2817    def best_objective_bound(self) -> float:
2818        """Returns the best lower (upper) bound found when min(max)imizing."""
2819        if not self.has_response():
2820            raise RuntimeError("solve() has not been called.")
2821        return self.BestObjectiveBound()

Returns the best lower (upper) bound found when min(max)imizing.

num_booleans: int
2823    @property
2824    def num_booleans(self) -> int:
2825        """Returns the number of boolean variables managed by the SAT solver."""
2826        if not self.has_response():
2827            raise RuntimeError("solve() has not been called.")
2828        return self.NumBooleans()

Returns the number of boolean variables managed by the SAT solver.

num_conflicts: int
2830    @property
2831    def num_conflicts(self) -> int:
2832        """Returns the number of conflicts since the creation of the solver."""
2833        if not self.has_response():
2834            raise RuntimeError("solve() has not been called.")
2835        return self.NumConflicts()

Returns the number of conflicts since the creation of the solver.

num_branches: int
2837    @property
2838    def num_branches(self) -> int:
2839        """Returns the number of search branches explored by the solver."""
2840        if not self.has_response():
2841            raise RuntimeError("solve() has not been called.")
2842        return self.NumBranches()

Returns the number of search branches explored by the solver.

num_integer_propagations: int
2844    @property
2845    def num_integer_propagations(self) -> int:
2846        """Returns the number of integer propagations done by the solver."""
2847        if not self.has_response():
2848            raise RuntimeError("solve() has not been called.")
2849        return self.NumIntegerPropagations()

Returns the number of integer propagations done by the solver.

num_boolean_propagations: int
2851    @property
2852    def num_boolean_propagations(self) -> int:
2853        """Returns the number of Boolean propagations done by the solver."""
2854        if not self.has_response():
2855            raise RuntimeError("solve() has not been called.")
2856        return self.NumBooleanPropagations()

Returns the number of Boolean propagations done by the solver.

deterministic_time: float
2858    @property
2859    def deterministic_time(self) -> float:
2860        """Returns the determistic time in seconds since the creation of the solver."""
2861        if not self.has_response():
2862            raise RuntimeError("solve() has not been called.")
2863        return self.DeterministicTime()

Returns the determistic time in seconds since the creation of the solver.

wall_time: float
2865    @property
2866    def wall_time(self) -> float:
2867        """Returns the wall time in seconds since the creation of the solver."""
2868        if not self.has_response():
2869            raise RuntimeError("solve() has not been called.")
2870        return self.WallTime()

Returns the wall time in seconds since the creation of the solver.

user_time: float
2872    @property
2873    def user_time(self) -> float:
2874        """Returns the user time in seconds since the creation of the solver."""
2875        if not self.has_response():
2876            raise RuntimeError("solve() has not been called.")
2877        return self.UserTime()

Returns the user time in seconds since the creation of the solver.

response_proto: ortools.sat.cp_model_pb2.CpSolverResponse
2879    @property
2880    def response_proto(self) -> cp_model_pb2.CpSolverResponse:
2881        """Returns the response object."""
2882        if not self.has_response():
2883            raise RuntimeError("solve() has not been called.")
2884        return self.Response()

Returns the response object.

class ObjectiveSolutionPrinter(CpSolverSolutionCallback):
2887class ObjectiveSolutionPrinter(CpSolverSolutionCallback):
2888    """Display the objective value and time of intermediate solutions."""
2889
2890    def __init__(self) -> None:
2891        CpSolverSolutionCallback.__init__(self)
2892        self.__solution_count = 0
2893        self.__start_time = time.time()
2894
2895    def on_solution_callback(self) -> None:
2896        """Called on each new solution."""
2897        current_time = time.time()
2898        obj = self.objective_value
2899        print(
2900            f"Solution {self.__solution_count}, time ="
2901            f" {current_time - self.__start_time:0.2f} s, objective = {obj}",
2902            flush=True,
2903        )
2904        self.__solution_count += 1
2905
2906    def solution_count(self) -> int:
2907        """Returns the number of solutions found."""
2908        return self.__solution_count

Display the objective value and time of intermediate solutions.

ObjectiveSolutionPrinter()
2890    def __init__(self) -> None:
2891        CpSolverSolutionCallback.__init__(self)
2892        self.__solution_count = 0
2893        self.__start_time = time.time()
def on_solution_callback(self) -> None:
2895    def on_solution_callback(self) -> None:
2896        """Called on each new solution."""
2897        current_time = time.time()
2898        obj = self.objective_value
2899        print(
2900            f"Solution {self.__solution_count}, time ="
2901            f" {current_time - self.__start_time:0.2f} s, objective = {obj}",
2902            flush=True,
2903        )
2904        self.__solution_count += 1

Called on each new solution.

def solution_count(self) -> int:
2906    def solution_count(self) -> int:
2907        """Returns the number of solutions found."""
2908        return self.__solution_count

Returns the number of solutions found.

class VarArrayAndObjectiveSolutionPrinter(CpSolverSolutionCallback):
2911class VarArrayAndObjectiveSolutionPrinter(CpSolverSolutionCallback):
2912    """Print intermediate solutions (objective, variable values, time)."""
2913
2914    def __init__(self, variables: Sequence[IntVar]) -> None:
2915        CpSolverSolutionCallback.__init__(self)
2916        self.__variables: Sequence[IntVar] = variables
2917        self.__solution_count: int = 0
2918        self.__start_time: float = time.time()
2919
2920    def on_solution_callback(self) -> None:
2921        """Called on each new solution."""
2922        current_time = time.time()
2923        obj = self.objective_value
2924        print(
2925            f"Solution {self.__solution_count}, time ="
2926            f" {current_time - self.__start_time:0.2f} s, objective = {obj}"
2927        )
2928        for v in self.__variables:
2929            print(f"  {v} = {self.value(v)}", end=" ")
2930        print(flush=True)
2931        self.__solution_count += 1
2932
2933    @property
2934    def solution_count(self) -> int:
2935        """Returns the number of solutions found."""
2936        return self.__solution_count

Print intermediate solutions (objective, variable values, time).

VarArrayAndObjectiveSolutionPrinter(variables: Sequence[IntVar])
2914    def __init__(self, variables: Sequence[IntVar]) -> None:
2915        CpSolverSolutionCallback.__init__(self)
2916        self.__variables: Sequence[IntVar] = variables
2917        self.__solution_count: int = 0
2918        self.__start_time: float = time.time()
def on_solution_callback(self) -> None:
2920    def on_solution_callback(self) -> None:
2921        """Called on each new solution."""
2922        current_time = time.time()
2923        obj = self.objective_value
2924        print(
2925            f"Solution {self.__solution_count}, time ="
2926            f" {current_time - self.__start_time:0.2f} s, objective = {obj}"
2927        )
2928        for v in self.__variables:
2929            print(f"  {v} = {self.value(v)}", end=" ")
2930        print(flush=True)
2931        self.__solution_count += 1

Called on each new solution.

solution_count: int
2933    @property
2934    def solution_count(self) -> int:
2935        """Returns the number of solutions found."""
2936        return self.__solution_count

Returns the number of solutions found.

class VarArraySolutionPrinter(CpSolverSolutionCallback):
2939class VarArraySolutionPrinter(CpSolverSolutionCallback):
2940    """Print intermediate solutions (variable values, time)."""
2941
2942    def __init__(self, variables: Sequence[IntVar]) -> None:
2943        CpSolverSolutionCallback.__init__(self)
2944        self.__variables: Sequence[IntVar] = variables
2945        self.__solution_count: int = 0
2946        self.__start_time: float = time.time()
2947
2948    def on_solution_callback(self) -> None:
2949        """Called on each new solution."""
2950        current_time = time.time()
2951        print(
2952            f"Solution {self.__solution_count}, time ="
2953            f" {current_time - self.__start_time:0.2f} s"
2954        )
2955        for v in self.__variables:
2956            print(f"  {v} = {self.value(v)}", end=" ")
2957        print(flush=True)
2958        self.__solution_count += 1
2959
2960    @property
2961    def solution_count(self) -> int:
2962        """Returns the number of solutions found."""
2963        return self.__solution_count

Print intermediate solutions (variable values, time).

VarArraySolutionPrinter(variables: Sequence[IntVar])
2942    def __init__(self, variables: Sequence[IntVar]) -> None:
2943        CpSolverSolutionCallback.__init__(self)
2944        self.__variables: Sequence[IntVar] = variables
2945        self.__solution_count: int = 0
2946        self.__start_time: float = time.time()
def on_solution_callback(self) -> None:
2948    def on_solution_callback(self) -> None:
2949        """Called on each new solution."""
2950        current_time = time.time()
2951        print(
2952            f"Solution {self.__solution_count}, time ="
2953            f" {current_time - self.__start_time:0.2f} s"
2954        )
2955        for v in self.__variables:
2956            print(f"  {v} = {self.value(v)}", end=" ")
2957        print(flush=True)
2958        self.__solution_count += 1

Called on each new solution.

solution_count: int
2960    @property
2961    def solution_count(self) -> int:
2962        """Returns the number of solutions found."""
2963        return self.__solution_count

Returns the number of solutions found.