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.
- .CpSolverSolutionCallback">
CpSolverSolutionCallback: A general method for implementing callbacks. - .ObjectiveSolutionPrinter">
ObjectiveSolutionPrinter: Print objective values and elapsed time for intermediate solutions. - .VarArraySolutionPrinter">
VarArraySolutionPrinter: Print intermediate solutions (variable values, time). - [
VarArrayAndObjectiveSolutionPrinter] (#cp_model.VarArrayAndObjectiveSolutionPrinter): Print both intermediate solutions and objective values.
Additional methods for solving CP-SAT models:
- .Constraint">
Constraint: A few utility methods for modifying constraints created byCpModel. - .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#!/usr/bin/env python3 2# Copyright 2010-2025 Google LLC 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Methods for building and solving CP-SAT models. 16 17The following two sections describe the main 18methods for building and solving CP-SAT models. 19 20* [`CpModel`](#cp_model.CpModel): Methods for creating models, including 21 variables and constraints. 22* [`CpSolver`](#cp_model.CpSolver): Methods for solving a model and evaluating 23 solutions. 24 25The following methods implement callbacks that the 26solver calls each time it finds a new solution. 27 28* [`CpSolverSolutionCallback`](#cp_model.CpSolverSolutionCallback): 29 A general method for implementing callbacks. 30* [`ObjectiveSolutionPrinter`](#cp_model.ObjectiveSolutionPrinter): 31 Print objective values and elapsed time for intermediate solutions. 32* [`VarArraySolutionPrinter`](#cp_model.VarArraySolutionPrinter): 33 Print intermediate solutions (variable values, time). 34* [`VarArrayAndObjectiveSolutionPrinter`] 35 (#cp_model.VarArrayAndObjectiveSolutionPrinter): 36 Print both intermediate solutions and objective values. 37 38Additional methods for solving CP-SAT models: 39 40* [`Constraint`](#cp_model.Constraint): A few utility methods for modifying 41 constraints created by `CpModel`. 42* [`LinearExpr`](#lineacp_model.LinearExpr): Methods for creating constraints 43 and the objective from large arrays of coefficients. 44 45Other methods and functions listed are primarily used for developing OR-Tools, 46rather than for solving specific optimization problems. 47""" 48 49from collections.abc import Callable, Iterable, Sequence 50import copy 51import threading 52import time 53from typing import ( 54 Any, 55 Optional, 56 Union, 57 overload, 58) 59import warnings 60 61import numpy as np 62import pandas as pd 63 64from ortools.sat.python import cp_model_helper as cmh 65from ortools.util.python import sorted_interval_list 66 67# Import external types. 68BoundedLinearExpression = cmh.BoundedLinearExpression 69Constraint = cmh.Constraint 70CpModelProto = cmh.CpModelProto 71CpSolverResponse = cmh.CpSolverResponse 72CpSolverStatus = cmh.CpSolverStatus 73Domain = sorted_interval_list.Domain 74FlatFloatExpr = cmh.FlatFloatExpr 75FlatIntExpr = cmh.FlatIntExpr 76IntervalVar = cmh.IntervalVar 77IntVar = cmh.IntVar 78LinearExpr = cmh.LinearExpr 79NotBooleanVariable = cmh.NotBooleanVariable 80SatParameters = cmh.SatParameters 81 82 83# The classes below allow linear expressions to be expressed naturally with the 84# usual arithmetic operators + - * / and with constant numbers, which makes the 85# python API very intuitive. See../ samples/*.py for examples. 86 87INT_MIN = -(2**63) # hardcoded to be platform independent. 88INT_MAX = 2**63 - 1 89INT32_MIN = -(2**31) 90INT32_MAX = 2**31 - 1 91 92# CpSolver status (exported to avoid importing cp_model_cp2). 93UNKNOWN = cmh.CpSolverStatus.UNKNOWN 94UNKNOWN = cmh.CpSolverStatus.UNKNOWN 95MODEL_INVALID = cmh.CpSolverStatus.MODEL_INVALID 96FEASIBLE = cmh.CpSolverStatus.FEASIBLE 97INFEASIBLE = cmh.CpSolverStatus.INFEASIBLE 98OPTIMAL = cmh.CpSolverStatus.OPTIMAL 99 100# Variable selection strategy 101CHOOSE_FIRST = cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_FIRST 102CHOOSE_LOWEST_MIN = ( 103 cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_LOWEST_MIN 104) 105CHOOSE_HIGHEST_MAX = ( 106 cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_HIGHEST_MAX 107) 108CHOOSE_MIN_DOMAIN_SIZE = ( 109 cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_MIN_DOMAIN_SIZE 110) 111CHOOSE_MAX_DOMAIN_SIZE = ( 112 cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_MAX_DOMAIN_SIZE 113) 114 115# Domain reduction strategy 116SELECT_MIN_VALUE = cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_MIN_VALUE 117SELECT_MAX_VALUE = cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_MAX_VALUE 118SELECT_LOWER_HALF = cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_LOWER_HALF 119SELECT_UPPER_HALF = cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_UPPER_HALF 120SELECT_MEDIAN_VALUE = ( 121 cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_MEDIAN_VALUE 122) 123SELECT_RANDOM_HALF = ( 124 cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_RANDOM_HALF 125) 126 127# Search branching 128AUTOMATIC_SEARCH = cmh.SatParameters.SearchBranching.AUTOMATIC_SEARCH 129FIXED_SEARCH = cmh.SatParameters.SearchBranching.FIXED_SEARCH 130PORTFOLIO_SEARCH = cmh.SatParameters.SearchBranching.PORTFOLIO_SEARCH 131LP_SEARCH = cmh.SatParameters.SearchBranching.LP_SEARCH 132PSEUDO_COST_SEARCH = cmh.SatParameters.SearchBranching.PSEUDO_COST_SEARCH 133PORTFOLIO_WITH_QUICK_RESTART_SEARCH = ( 134 cmh.SatParameters.SearchBranching.PORTFOLIO_WITH_QUICK_RESTART_SEARCH 135) 136HINT_SEARCH = cmh.SatParameters.SearchBranching.HINT_SEARCH 137PARTIAL_FIXED_SEARCH = cmh.SatParameters.SearchBranching.PARTIAL_FIXED_SEARCH 138RANDOMIZED_SEARCH = cmh.SatParameters.SearchBranching.RANDOMIZED_SEARCH 139 140# Type aliases 141IntegralT = Union[int, np.int8, np.uint8, np.int32, np.uint32, np.int64, np.uint64] 142IntegralTypes = ( 143 int, 144 np.int8, 145 np.uint8, 146 np.int32, 147 np.uint32, 148 np.int64, 149 np.uint64, 150) 151NumberT = Union[ 152 int, 153 float, 154 np.int8, 155 np.uint8, 156 np.int32, 157 np.uint32, 158 np.int64, 159 np.uint64, 160 np.double, 161] 162NumberTypes = ( 163 int, 164 float, 165 np.int8, 166 np.uint8, 167 np.int32, 168 np.uint32, 169 np.int64, 170 np.uint64, 171 np.double, 172) 173 174LiteralT = Union[cmh.Literal, IntegralT, bool] 175BoolVarT = cmh.Literal 176VariableT = Union["IntVar", IntegralT] 177 178# We need to add 'IntVar' for pytype. 179LinearExprT = Union[LinearExpr, "IntVar", IntegralT] 180ObjLinearExprT = Union[LinearExpr, NumberT] 181 182ArcT = tuple[IntegralT, IntegralT, LiteralT] 183_IndexOrSeries = Union[pd.Index, pd.Series] 184 185 186# Helper functions. 187enable_warnings = False 188 189# warnings.deprecated is python3.13+. Not compatible with Open Source (3.10+). 190# pylint: disable=g-bare-generic 191def deprecated(message: str) -> Callable[[Callable], Callable]: 192 """Decorator that warns about a deprecated function.""" 193 194 def deprecated_decorator(func) -> Callable: 195 def deprecated_func(*args, **kwargs): 196 if enable_warnings: 197 warnings.warn( 198 f"{func.__name__} is a deprecated function. {message}", 199 category=DeprecationWarning, 200 stacklevel=2, 201 ) 202 warnings.simplefilter("default", DeprecationWarning) 203 return func(*args, **kwargs) 204 205 return deprecated_func 206 207 return deprecated_decorator 208 209 210def deprecated_method(func, old_name: str) -> Callable: 211 """Wrapper that warns about a deprecated method.""" 212 213 def deprecated_func(*args, **kwargs) -> Any: 214 if enable_warnings: 215 warnings.warn( 216 f"{old_name} is a deprecated function. Use {func.__name__} instead.", 217 category=DeprecationWarning, 218 stacklevel=2, 219 ) 220 warnings.simplefilter("default", DeprecationWarning) 221 return func(*args, **kwargs) 222 223 return deprecated_func 224 225 226# pylint: enable=g-bare-generic 227 228 229def snake_case_to_camel_case(name: str) -> str: 230 """Converts a snake_case name to CamelCase.""" 231 words = name.split("_") 232 return ( 233 "".join(word.capitalize() for word in words) 234 .replace("2d", "2D") 235 .replace("Xor", "XOr") 236 ) 237 238 239def object_is_a_true_literal(literal: LiteralT) -> bool: 240 """Checks if literal is either True, or a Boolean literals fixed to True.""" 241 if isinstance(literal, IntVar): 242 proto = literal.proto 243 return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1 244 if isinstance(literal, cmh.NotBooleanVariable): 245 proto = literal.negated().proto 246 return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0 247 if isinstance(literal, (bool, np.bool_)): 248 return bool(literal) 249 if isinstance(literal, IntegralTypes): 250 literal_as_int = int(literal) 251 return literal_as_int == 1 or literal_as_int == ~False 252 return False 253 254 255def object_is_a_false_literal(literal: LiteralT) -> bool: 256 """Checks if literal is either False, or a Boolean literals fixed to False.""" 257 if isinstance(literal, IntVar): 258 proto = literal.proto 259 return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0 260 if isinstance(literal, cmh.NotBooleanVariable): 261 proto = literal.negated().proto 262 return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1 263 if isinstance(literal, (bool, np.bool_)): 264 return not bool(literal) 265 if isinstance(literal, IntegralTypes): 266 literal_as_int = int(literal) 267 return literal_as_int == 0 or literal_as_int == ~True 268 return False 269 270 271def _get_index(obj: _IndexOrSeries) -> pd.Index: 272 """Returns the indices of `obj` as a `pd.Index`.""" 273 if isinstance(obj, pd.Series): 274 return obj.index 275 return obj 276 277 278@overload 279def _convert_to_series_and_validate_index( 280 value_or_series: Union[LinearExprT, pd.Series], index: pd.Index 281) -> pd.Series: ... 282 283 284@overload 285def _convert_to_series_and_validate_index( 286 value_or_series: Union[LiteralT, pd.Series], index: pd.Index 287) -> pd.Series: ... 288 289 290@overload 291def _convert_to_series_and_validate_index( 292 value_or_series: Union[IntegralT, pd.Series], index: pd.Index 293) -> pd.Series: ... 294 295 296def _convert_to_series_and_validate_index(value_or_series, index): 297 """Returns a pd.Series of the given index with the corresponding values.""" 298 if isinstance(value_or_series, pd.Series): 299 if value_or_series.index.equals(index): 300 return value_or_series 301 else: 302 raise ValueError("index does not match") 303 return pd.Series(data=value_or_series, index=index) 304 305 306class CpModel(cmh.CpBaseModel): 307 """Methods for building a CP model. 308 309 Methods beginning with: 310 311 * ```new_``` create integer, boolean, or interval variables. 312 * ```add_``` create new constraints and add them to the model. 313 """ 314 315 def __init__(self, model_proto: Optional[cmh.CpModelProto] = None) -> None: 316 cmh.CpBaseModel.__init__(self, model_proto) 317 self._add_pre_pep8_methods() 318 319 # Naming. 320 @property 321 def name(self) -> str: 322 """Returns the name of the model.""" 323 if not self.model_proto or not self.model_proto.name: 324 return "" 325 return self.model_proto.name 326 327 @name.setter 328 def name(self, name: str): 329 """Sets the name of the model.""" 330 self.model_proto.name = name 331 332 # Integer variable. 333 def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: 334 """Create an integer variable with domain [lb, ub]. 335 336 The CP-SAT solver is limited to integer variables. If you have fractional 337 values, scale them up so that they become integers; if you have strings, 338 encode them as integers. 339 340 Args: 341 lb: Lower bound for the variable. 342 ub: Upper bound for the variable. 343 name: The name of the variable. 344 345 Returns: 346 a variable whose domain is [lb, ub]. 347 """ 348 return ( 349 IntVar(self.model_proto) 350 .with_name(name) 351 .with_domain(sorted_interval_list.Domain(lb, ub)) 352 ) 353 354 def new_int_var_from_domain( 355 self, domain: sorted_interval_list.Domain, name: str 356 ) -> IntVar: 357 """Create an integer variable from a domain. 358 359 A domain is a set of integers specified by a collection of intervals. 360 For example, `model.new_int_var_from_domain(cp_model. 361 Domain.from_intervals([[1, 2], [4, 6]]), 'x')` 362 363 Args: 364 domain: An instance of the Domain class. 365 name: The name of the variable. 366 367 Returns: 368 a variable whose domain is the given domain. 369 """ 370 return IntVar(self.model_proto).with_name(name).with_domain(domain) 371 372 def new_bool_var(self, name: str) -> IntVar: 373 """Creates a 0-1 variable with the given name.""" 374 return ( 375 IntVar(self.model_proto) 376 .with_name(name) 377 .with_domain(sorted_interval_list.Domain(0, 1)) 378 ) 379 380 def new_constant(self, value: IntegralT) -> IntVar: 381 """Declares a constant integer.""" 382 return IntVar(self.model_proto, self.get_or_make_index_from_constant(value)) 383 384 def new_int_var_series( 385 self, 386 name: str, 387 index: pd.Index, 388 lower_bounds: Union[IntegralT, pd.Series], 389 upper_bounds: Union[IntegralT, pd.Series], 390 ) -> pd.Series: 391 """Creates a series of (scalar-valued) variables with the given name. 392 393 Args: 394 name (str): Required. The name of the variable set. 395 index (pd.Index): Required. The index to use for the variable set. 396 lower_bounds (Union[int, pd.Series]): A lower bound for variables in the 397 set. If a `pd.Series` is passed in, it will be based on the 398 corresponding values of the pd.Series. 399 upper_bounds (Union[int, pd.Series]): An upper bound for variables in the 400 set. If a `pd.Series` is passed in, it will be based on the 401 corresponding values of the pd.Series. 402 403 Returns: 404 pd.Series: The variable set indexed by its corresponding dimensions. 405 406 Raises: 407 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 408 ValueError: if the `name` is not a valid identifier or already exists. 409 ValueError: if the `lowerbound` is greater than the `upperbound`. 410 ValueError: if the index of `lower_bound`, or `upper_bound` does not match 411 the input index. 412 """ 413 if not isinstance(index, pd.Index): 414 raise TypeError("Non-index object is used as index") 415 if not name.isidentifier(): 416 raise ValueError(f"name={name!r} is not a valid identifier") 417 if ( 418 isinstance(lower_bounds, IntegralTypes) 419 and isinstance(upper_bounds, IntegralTypes) 420 and lower_bounds > upper_bounds 421 ): 422 raise ValueError( 423 f"lower_bound={lower_bounds} is greater than" 424 f" upper_bound={upper_bounds} for variable set={name}" 425 ) 426 427 lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index) 428 upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index) 429 return pd.Series( 430 index=index, 431 data=[ 432 # pylint: disable=g-complex-comprehension 433 IntVar(self.model_proto) 434 .with_name(f"{name}[{i}]") 435 .with_domain( 436 sorted_interval_list.Domain(lower_bounds[i], upper_bounds[i]) 437 ) 438 for i in index 439 ], 440 ) 441 442 def new_bool_var_series( 443 self, 444 name: str, 445 index: pd.Index, 446 ) -> pd.Series: 447 """Creates a series of (scalar-valued) variables with the given name. 448 449 Args: 450 name (str): Required. The name of the variable set. 451 index (pd.Index): Required. The index to use for the variable set. 452 453 Returns: 454 pd.Series: The variable set indexed by its corresponding dimensions. 455 456 Raises: 457 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 458 ValueError: if the `name` is not a valid identifier or already exists. 459 """ 460 if not isinstance(index, pd.Index): 461 raise TypeError("Non-index object is used as index") 462 if not name.isidentifier(): 463 raise ValueError(f"name={name!r} is not a valid identifier") 464 return pd.Series( 465 index=index, 466 data=[ 467 # pylint: disable=g-complex-comprehension 468 IntVar(self.model_proto) 469 .with_name(f"{name}[{i}]") 470 .with_domain(sorted_interval_list.Domain(0, 1)) 471 for i in index 472 ], 473 ) 474 475 # Linear constraints. 476 477 def add_linear_constraint( 478 self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT 479 ) -> Constraint: 480 """Adds the constraint: `lb <= linear_expr <= ub`.""" 481 return self.add_linear_expression_in_domain( 482 linear_expr, sorted_interval_list.Domain(lb, ub) 483 ) 484 485 def add_linear_expression_in_domain( 486 self, 487 linear_expr: LinearExprT, 488 domain: sorted_interval_list.Domain, 489 ) -> Constraint: 490 """Adds the constraint: `linear_expr` in `domain`.""" 491 if isinstance(linear_expr, LinearExpr): 492 ble = BoundedLinearExpression(linear_expr, domain) 493 if not ble.ok: 494 raise TypeError( 495 "Cannot add a linear expression containing floating point" 496 f" coefficients or constants: {type(linear_expr).__name__!r}" 497 ) 498 return self._add_bounded_linear_expression(ble) 499 if isinstance(linear_expr, IntegralTypes): 500 if not domain.contains(int(linear_expr)): 501 return self.add_bool_or([]) # Evaluate to false. 502 else: 503 return self.add_bool_and([]) # Evaluate to true. 504 raise TypeError( 505 "not supported:" 506 f" CpModel.add_linear_expression_in_domain({type(linear_expr).__name__!r})" 507 ) 508 509 def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint: 510 """Adds a `BoundedLinearExpression` to the model. 511 512 Args: 513 ct: A [`BoundedLinearExpression`](#boundedlinearexpression). 514 515 Returns: 516 An instance of the `Constraint` class. 517 518 Raises: 519 TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean. 520 """ 521 if isinstance(ct, BoundedLinearExpression): 522 return self._add_bounded_linear_expression(ct) 523 if ct and self.is_boolean_value(ct): 524 return self.add_bool_or([True]) 525 if not ct and self.is_boolean_value(ct): 526 return self.add_bool_or([]) # Evaluate to false. 527 raise TypeError(f"not supported: CpModel.add({type(ct).__name__!r})") 528 529 # General Integer Constraints. 530 531 @overload 532 def add_all_different(self, expressions: Iterable[LinearExprT]) -> Constraint: ... 533 534 @overload 535 def add_all_different(self, *expressions: LinearExprT) -> Constraint: ... 536 537 def add_all_different(self, *expressions): 538 """Adds AllDifferent(expressions). 539 540 This constraint forces all expressions to have different values. 541 542 Args: 543 *expressions: simple expressions of the form a * var + constant. 544 545 Returns: 546 An instance of the `Constraint` class. 547 """ 548 return self._add_all_different(*expressions) 549 550 def add_element( 551 self, 552 index: LinearExprT, 553 expressions: Sequence[LinearExprT], 554 target: LinearExprT, 555 ) -> Constraint: 556 """Adds the element constraint: `expressions[index] == target`. 557 558 Args: 559 index: The index of the selected expression in the array. It must be an 560 affine expression (a * var + b). 561 expressions: A list of affine expressions. 562 target: The expression constrained to be equal to the selected expression. 563 It must be an affine expression (a * var + b). 564 565 Returns: 566 An instance of the `Constraint` class. 567 """ 568 569 if not expressions: 570 raise ValueError("add_element expects a non-empty expressions array") 571 572 if isinstance(index, IntegralTypes): 573 expression: LinearExprT = list(expressions)[int(index)] 574 return self.add(expression == target) 575 576 return self._add_element(index, expressions, target) 577 578 def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint: 579 """Adds Circuit(arcs). 580 581 Adds a circuit constraint from a sparse list of arcs that encode the graph. 582 583 A circuit is a unique Hamiltonian cycle in a subgraph of the total 584 graph. In case a node 'i' is not in the cycle, then there must be a 585 loop arc 'i -> i' associated with a true literal. Otherwise 586 this constraint will fail. 587 588 Args: 589 arcs: a list of arcs. An arc is a tuple (source_node, destination_node, 590 literal). The arc is selected in the circuit if the literal is true. 591 Both source_node and destination_node must be integers between 0 and the 592 number of nodes - 1. 593 594 Returns: 595 An instance of the `Constraint` class. 596 597 Raises: 598 ValueError: If the list of arcs is empty. 599 """ 600 if not arcs: 601 raise ValueError("add_circuit expects a non-empty array of arcs") 602 return self._add_circuit(arcs) 603 604 def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint: 605 """Adds a multiple circuit constraint, aka the 'VRP' constraint. 606 607 The direct graph where arc #i (from tails[i] to head[i]) is present iff 608 literals[i] is true must satisfy this set of properties: 609 - #incoming arcs == 1 except for node 0. 610 - #outgoing arcs == 1 except for node 0. 611 - for node zero, #incoming arcs == #outgoing arcs. 612 - There are no duplicate arcs. 613 - Self-arcs are allowed except for node 0. 614 - There is no cycle in this graph, except through node 0. 615 616 Args: 617 arcs: a list of arcs. An arc is a tuple (source_node, destination_node, 618 literal). The arc is selected in the circuit if the literal is true. 619 Both source_node and destination_node must be integers between 0 and the 620 number of nodes - 1. 621 622 Returns: 623 An instance of the `Constraint` class. 624 625 Raises: 626 ValueError: If the list of arcs is empty. 627 """ 628 if not arcs: 629 raise ValueError("add_multiple_circuit expects a non-empty array of arcs") 630 return self._add_routes(arcs) 631 632 def add_allowed_assignments( 633 self, 634 expressions: Sequence[LinearExprT], 635 tuples_list: Iterable[Sequence[IntegralT]], 636 ) -> Constraint: 637 """Adds AllowedAssignments(expressions, tuples_list). 638 639 An AllowedAssignments constraint is a constraint on an array of affine 640 expressions, which requires that when all expressions are assigned values, 641 the 642 resulting array equals one of the tuples in `tuple_list`. 643 644 Args: 645 expressions: A list of affine expressions (a * var + b). 646 tuples_list: A list of admissible tuples. Each tuple must have the same 647 length as the expressions, and the ith value of a tuple corresponds to 648 the ith expression. 649 650 Returns: 651 An instance of the `Constraint` class. 652 653 Raises: 654 TypeError: If a tuple does not have the same size as the list of 655 expressions. 656 ValueError: If the array of expressions is empty. 657 """ 658 659 if not expressions: 660 raise ValueError( 661 "add_allowed_assignments expects a non-empty expressions array" 662 ) 663 664 return self._add_table(expressions, tuples_list, False) 665 666 def add_forbidden_assignments( 667 self, 668 expressions: Sequence[LinearExprT], 669 tuples_list: Iterable[Sequence[IntegralT]], 670 ) -> Constraint: 671 """Adds add_forbidden_assignments(expressions, [tuples_list]). 672 673 A ForbiddenAssignments constraint is a constraint on an array of affine 674 expressions where the list of impossible combinations is provided in the 675 tuples list. 676 677 Args: 678 expressions: A list of affine expressions (a * var + b). 679 tuples_list: A list of forbidden tuples. Each tuple must have the same 680 length as the expressions, and the *i*th value of a tuple corresponds to 681 the *i*th expression. 682 683 Returns: 684 An instance of the `Constraint` class. 685 686 Raises: 687 TypeError: If a tuple does not have the same size as the list of 688 expressions. 689 ValueError: If the array of expressions is empty. 690 """ 691 692 if not expressions: 693 raise ValueError( 694 "add_forbidden_assignments expects a non-empty expressions array" 695 ) 696 697 return self._add_table(expressions, tuples_list, True) 698 699 def add_automaton( 700 self, 701 transition_expressions: Sequence[LinearExprT], 702 starting_state: IntegralT, 703 final_states: Sequence[IntegralT], 704 transition_triples: Sequence[tuple[IntegralT, IntegralT, IntegralT]], 705 ) -> Constraint: 706 """Adds an automaton constraint. 707 708 An automaton constraint takes a list of affine expressions (a * var + b) (of 709 size *n*), an initial state, a set of final states, and a set of 710 transitions. A transition is a triplet (*tail*, *transition*, *head*), where 711 *tail* and *head* are states, and *transition* is the label of an arc from 712 *head* to *tail*, corresponding to the value of one expression in the list 713 of 714 expressions. 715 716 This automaton will be unrolled into a flow with *n* + 1 phases. Each phase 717 contains the possible states of the automaton. The first state contains the 718 initial state. The last phase contains the final states. 719 720 Between two consecutive phases *i* and *i* + 1, the automaton creates a set 721 of arcs. For each transition (*tail*, *transition*, *head*), it will add 722 an arc from the state *tail* of phase *i* and the state *head* of phase 723 *i* + 1. This arc is labeled by the value *transition* of the expression 724 `expressions[i]`. That is, this arc can only be selected if `expressions[i]` 725 is assigned the value *transition*. 726 727 A feasible solution of this constraint is an assignment of expressions such 728 that, starting from the initial state in phase 0, there is a path labeled by 729 the values of the expressions that ends in one of the final states in the 730 final phase. 731 732 Args: 733 transition_expressions: A non-empty list of affine expressions (a * var + 734 b) whose values correspond to the labels of the arcs traversed by the 735 automaton. 736 starting_state: The initial state of the automaton. 737 final_states: A non-empty list of admissible final states. 738 transition_triples: A list of transitions for the automaton, in the 739 following format (current_state, variable_value, next_state). 740 741 Returns: 742 An instance of the `Constraint` class. 743 744 Raises: 745 ValueError: if `transition_expressions`, `final_states`, or 746 `transition_triples` are empty. 747 """ 748 749 if not transition_expressions: 750 raise ValueError( 751 "add_automaton expects a non-empty transition_expressions array" 752 ) 753 if not final_states: 754 raise ValueError("add_automaton expects some final states") 755 756 if not transition_triples: 757 raise ValueError("add_automaton expects some transition triples") 758 759 return self._add_automaton( 760 transition_expressions, 761 starting_state, 762 final_states, 763 transition_triples, 764 ) 765 766 def add_inverse( 767 self, 768 variables: Sequence[VariableT], 769 inverse_variables: Sequence[VariableT], 770 ) -> Constraint: 771 """Adds Inverse(variables, inverse_variables). 772 773 An inverse constraint enforces that if `variables[i]` is assigned a value 774 `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa. 775 776 Args: 777 variables: An array of integer variables. 778 inverse_variables: An array of integer variables. 779 780 Returns: 781 An instance of the `Constraint` class. 782 783 Raises: 784 TypeError: if variables and inverse_variables have different lengths, or 785 if they are empty. 786 """ 787 788 if not variables or not inverse_variables: 789 raise TypeError("The Inverse constraint does not accept empty arrays") 790 if len(variables) != len(inverse_variables): 791 raise TypeError( 792 "In the inverse constraint, the two array variables and" 793 " inverse_variables must have the same length." 794 ) 795 return self._add_inverse(variables, inverse_variables) 796 797 def add_reservoir_constraint( 798 self, 799 times: Sequence[LinearExprT], 800 level_changes: Sequence[LinearExprT], 801 min_level: int, 802 max_level: int, 803 ) -> Constraint: 804 """Adds Reservoir(times, level_changes, min_level, max_level). 805 806 Maintains a reservoir level within bounds. The water level starts at 0, and 807 at any time, it must be between min_level and max_level. 808 809 If the affine expression `times[i]` is assigned a value t, then the current 810 level changes by `level_changes[i]`, which is constant, at time t. 811 812 Note that min level must be <= 0, and the max level must be >= 0. Please 813 use fixed level_changes to simulate initial state. 814 815 Therefore, at any time: 816 sum(level_changes[i] if times[i] <= t) in [min_level, max_level] 817 818 Args: 819 times: A list of 1-var affine expressions (a * x + b) which specify the 820 time of the filling or emptying the reservoir. 821 level_changes: A list of integer values that specifies the amount of the 822 emptying or filling. Currently, variable demands are not supported. 823 min_level: At any time, the level of the reservoir must be greater or 824 equal than the min level. 825 max_level: At any time, the level of the reservoir must be less or equal 826 than the max level. 827 828 Returns: 829 An instance of the `Constraint` class. 830 831 Raises: 832 ValueError: if max_level < min_level. 833 834 ValueError: if max_level < 0. 835 836 ValueError: if min_level > 0 837 """ 838 839 return self._add_reservoir( 840 times, 841 level_changes, 842 [], 843 min_level, 844 max_level, 845 ) 846 847 def add_reservoir_constraint_with_active( 848 self, 849 times: Sequence[LinearExprT], 850 level_changes: Sequence[LinearExprT], 851 actives: Sequence[LiteralT], 852 min_level: int, 853 max_level: int, 854 ) -> Constraint: 855 """Adds Reservoir(times, level_changes, actives, min_level, max_level). 856 857 Maintains a reservoir level within bounds. The water level starts at 0, and 858 at any time, it must be between min_level and max_level. 859 860 If the variable `times[i]` is assigned a value t, and `actives[i]` is 861 `True`, then the current level changes by `level_changes[i]`, which is 862 constant, 863 at time t. 864 865 Note that min level must be <= 0, and the max level must be >= 0. Please 866 use fixed level_changes to simulate initial state. 867 868 Therefore, at any time: 869 sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level, 870 max_level] 871 872 873 The array of boolean variables 'actives', if defined, indicates which 874 actions are actually performed. 875 876 Args: 877 times: A list of 1-var affine expressions (a * x + b) which specify the 878 time of the filling or emptying the reservoir. 879 level_changes: A list of integer values that specifies the amount of the 880 emptying or filling. Currently, variable demands are not supported. 881 actives: a list of boolean variables. They indicates if the 882 emptying/refilling events actually take place. 883 min_level: At any time, the level of the reservoir must be greater or 884 equal than the min level. 885 max_level: At any time, the level of the reservoir must be less or equal 886 than the max level. 887 888 Returns: 889 An instance of the `Constraint` class. 890 891 Raises: 892 ValueError: if max_level < min_level. 893 894 ValueError: if max_level < 0. 895 896 ValueError: if min_level > 0 897 """ 898 899 if max_level < min_level: 900 raise ValueError("Reservoir constraint must have a max_level >= min_level") 901 902 if max_level < 0: 903 raise ValueError("Reservoir constraint must have a max_level >= 0") 904 905 if min_level > 0: 906 raise ValueError("Reservoir constraint must have a min_level <= 0") 907 908 if not times: 909 raise ValueError("Reservoir constraint must have a non-empty times array") 910 911 return self._add_reservoir( 912 times, 913 level_changes, 914 actives, 915 min_level, 916 max_level, 917 ) 918 919 def add_map_domain( 920 self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0 921 ): 922 """Adds `var == i + offset <=> bool_var_array[i] == true for all i`.""" 923 for i, bool_var in enumerate(bool_var_array): 924 self.add(var == i + offset).only_enforce_if(bool_var) 925 self.add(var != i + offset).only_enforce_if(~bool_var) 926 927 def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint: 928 """Adds `a => b` (`a` implies `b`).""" 929 return self.add_bool_and(b).only_enforce_if(a) 930 931 @overload 932 def add_bool_or(self, literals: Iterable[LiteralT]) -> Constraint: ... 933 934 @overload 935 def add_bool_or(self, *literals: LiteralT) -> Constraint: ... 936 937 def add_bool_or(self, *literals): 938 """Adds `Or(literals) == true`: sum(literals) >= 1.""" 939 return self._add_bool_argument_constraint( 940 cmh.BoolArgumentConstraint.bool_or, *literals 941 ) 942 943 @overload 944 def add_at_least_one(self, literals: Iterable[LiteralT]) -> Constraint: ... 945 946 @overload 947 def add_at_least_one(self, *literals: LiteralT) -> Constraint: ... 948 949 def add_at_least_one(self, *literals): 950 """Same as `add_bool_or`: `sum(literals) >= 1`.""" 951 return self._add_bool_argument_constraint( 952 cmh.BoolArgumentConstraint.bool_or, *literals 953 ) 954 955 @overload 956 def add_at_most_one(self, literals: Iterable[LiteralT]) -> Constraint: ... 957 958 @overload 959 def add_at_most_one(self, *literals: LiteralT) -> Constraint: ... 960 961 def add_at_most_one(self, *literals) -> Constraint: 962 """Adds `AtMostOne(literals)`: `sum(literals) <= 1`.""" 963 return self._add_bool_argument_constraint( 964 cmh.BoolArgumentConstraint.at_most_one, *literals 965 ) 966 967 @overload 968 def add_exactly_one(self, literals: Iterable[LiteralT]) -> Constraint: ... 969 970 @overload 971 def add_exactly_one(self, *literals: LiteralT) -> Constraint: ... 972 973 def add_exactly_one(self, *literals): 974 """Adds `ExactlyOne(literals)`: `sum(literals) == 1`.""" 975 return self._add_bool_argument_constraint( 976 cmh.BoolArgumentConstraint.exactly_one, *literals 977 ) 978 979 @overload 980 def add_bool_and(self, literals: Iterable[LiteralT]) -> Constraint: ... 981 982 @overload 983 def add_bool_and(self, *literals: LiteralT) -> Constraint: ... 984 985 def add_bool_and(self, *literals): 986 """Adds `And(literals) == true`.""" 987 return self._add_bool_argument_constraint( 988 cmh.BoolArgumentConstraint.bool_and, *literals 989 ) 990 991 @overload 992 def add_bool_xor(self, literals: Iterable[LiteralT]) -> Constraint: ... 993 994 @overload 995 def add_bool_xor(self, *literals: LiteralT) -> Constraint: ... 996 997 def add_bool_xor(self, *literals): 998 """Adds `XOr(literals) == true`. 999 1000 In contrast to add_bool_or and add_bool_and, it does not support 1001 .only_enforce_if(). 1002 1003 Args: 1004 *literals: the list of literals in the constraint. 1005 1006 Returns: 1007 An `Constraint` object. 1008 """ 1009 return self._add_bool_argument_constraint( 1010 cmh.BoolArgumentConstraint.bool_xor, *literals 1011 ) 1012 1013 @overload 1014 def add_min_equality( 1015 self, target: LinearExprT, expressions: Iterable[LinearExprT] 1016 ) -> Constraint: ... 1017 1018 @overload 1019 def add_min_equality( 1020 self, target: LinearExprT, *expressions: LinearExprT 1021 ) -> Constraint: ... 1022 1023 def add_min_equality(self, target, *expressions) -> Constraint: 1024 """Adds `target == Min(expressions)`.""" 1025 return self._add_linear_argument_constraint( 1026 cmh.LinearArgumentConstraint.min, target, *expressions 1027 ) 1028 1029 @overload 1030 def add_max_equality( 1031 self, target: LinearExprT, expressions: Iterable[LinearExprT] 1032 ) -> Constraint: ... 1033 1034 @overload 1035 def add_max_equality( 1036 self, target: LinearExprT, *expressions: LinearExprT 1037 ) -> Constraint: ... 1038 1039 def add_max_equality(self, target, *expressions) -> Constraint: 1040 """Adds `target == Max(expressions)`.""" 1041 return self._add_linear_argument_constraint( 1042 cmh.LinearArgumentConstraint.max, target, *expressions 1043 ) 1044 1045 def add_division_equality( 1046 self, target: LinearExprT, num: LinearExprT, denom: LinearExprT 1047 ) -> Constraint: 1048 """Adds `target == num // denom` (integer division rounded towards 0).""" 1049 return self._add_linear_argument_constraint( 1050 cmh.LinearArgumentConstraint.div, target, [num, denom] 1051 ) 1052 1053 def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint: 1054 """Adds `target == Abs(expr)`.""" 1055 return self._add_linear_argument_constraint( 1056 cmh.LinearArgumentConstraint.max, target, [expr, -expr] 1057 ) 1058 1059 def add_modulo_equality( 1060 self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT 1061 ) -> Constraint: 1062 """Adds `target = expr % mod`. 1063 1064 It uses the C convention, that is the result is the remainder of the 1065 integral division rounded towards 0. 1066 1067 For example: 1068 * 10 % 3 = 1 1069 * -10 % 3 = -1 1070 * 10 % -3 = 1 1071 * -10 % -3 = -1 1072 1073 Args: 1074 target: the target expression. 1075 expr: the expression to compute the modulo of. 1076 mod: the modulus expression. 1077 1078 Returns: 1079 A `Constraint` object. 1080 """ 1081 return self._add_linear_argument_constraint( 1082 cmh.LinearArgumentConstraint.mod, target, [expr, mod] 1083 ) 1084 1085 def add_multiplication_equality( 1086 self, 1087 target: LinearExprT, 1088 *expressions: Union[Iterable[LinearExprT], LinearExprT], 1089 ) -> Constraint: 1090 """Adds `target == expressions[0] * .. * expressions[n]`.""" 1091 return self._add_linear_argument_constraint( 1092 cmh.LinearArgumentConstraint.prod, target, *expressions 1093 ) 1094 1095 # Scheduling support 1096 1097 def new_interval_var( 1098 self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str 1099 ) -> IntervalVar: 1100 """Creates an interval variable from start, size, and end. 1101 1102 An interval variable is a constraint, that is itself used in other 1103 constraints like NoOverlap. 1104 1105 Internally, it ensures that `start + size == end`. 1106 1107 Args: 1108 start: The start of the interval. It must be of the form a * var + b. 1109 size: The size of the interval. It must be of the form a * var + b. 1110 end: The end of the interval. It must be of the form a * var + b. 1111 name: The name of the interval variable. 1112 1113 Returns: 1114 An `IntervalVar` object. 1115 """ 1116 return self._new_interval_var(name, start, size, end, []) 1117 1118 def new_interval_var_series( 1119 self, 1120 name: str, 1121 index: pd.Index, 1122 starts: Union[LinearExprT, pd.Series], 1123 sizes: Union[LinearExprT, pd.Series], 1124 ends: Union[LinearExprT, pd.Series], 1125 ) -> pd.Series: 1126 """Creates a series of interval variables with the given name. 1127 1128 Args: 1129 name (str): Required. The name of the variable set. 1130 index (pd.Index): Required. The index to use for the variable set. 1131 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1132 set. If a `pd.Series` is passed in, it will be based on the 1133 corresponding values of the pd.Series. 1134 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the 1135 set. If a `pd.Series` is passed in, it will be based on the 1136 corresponding values of the pd.Series. 1137 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the 1138 set. If a `pd.Series` is passed in, it will be based on the 1139 corresponding values of the pd.Series. 1140 1141 Returns: 1142 pd.Series: The interval variable set indexed by its corresponding 1143 dimensions. 1144 1145 Raises: 1146 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1147 ValueError: if the `name` is not a valid identifier or already exists. 1148 ValueError: if the all the indexes do not match. 1149 """ 1150 if not isinstance(index, pd.Index): 1151 raise TypeError("Non-index object is used as index") 1152 if not name.isidentifier(): 1153 raise ValueError(f"name={name!r} is not a valid identifier") 1154 1155 starts = _convert_to_series_and_validate_index(starts, index) 1156 sizes = _convert_to_series_and_validate_index(sizes, index) 1157 ends = _convert_to_series_and_validate_index(ends, index) 1158 interval_array = [] 1159 for i in index: 1160 interval_array.append( 1161 self.new_interval_var( 1162 start=starts[i], 1163 size=sizes[i], 1164 end=ends[i], 1165 name=f"{name}[{i}]", 1166 ) 1167 ) 1168 return pd.Series(index=index, data=interval_array) 1169 1170 def new_fixed_size_interval_var( 1171 self, start: LinearExprT, size: IntegralT, name: str 1172 ) -> IntervalVar: 1173 """Creates an interval variable from start, and a fixed size. 1174 1175 An interval variable is a constraint, that is itself used in other 1176 constraints like NoOverlap. 1177 1178 Args: 1179 start: The start of the interval. It must be of the form a * var + b. 1180 size: The size of the interval. It must be an integer value. 1181 name: The name of the interval variable. 1182 1183 Returns: 1184 An `IntervalVar` object. 1185 """ 1186 return self._new_interval_var(name, start, size, start + size, []) 1187 1188 def new_fixed_size_interval_var_series( 1189 self, 1190 name: str, 1191 index: pd.Index, 1192 starts: Union[LinearExprT, pd.Series], 1193 sizes: Union[IntegralT, pd.Series], 1194 ) -> pd.Series: 1195 """Creates a series of interval variables with the given name. 1196 1197 Args: 1198 name (str): Required. The name of the variable set. 1199 index (pd.Index): Required. The index to use for the variable set. 1200 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1201 set. If a `pd.Series` is passed in, it will be based on the 1202 corresponding values of the pd.Series. 1203 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in 1204 the set. If a `pd.Series` is passed in, it will be based on the 1205 corresponding values of the pd.Series. 1206 1207 Returns: 1208 pd.Series: The interval variable set indexed by its corresponding 1209 dimensions. 1210 1211 Raises: 1212 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1213 ValueError: if the `name` is not a valid identifier or already exists. 1214 ValueError: if the all the indexes do not match. 1215 """ 1216 if not isinstance(index, pd.Index): 1217 raise TypeError("Non-index object is used as index") 1218 if not name.isidentifier(): 1219 raise ValueError(f"name={name!r} is not a valid identifier") 1220 1221 starts = _convert_to_series_and_validate_index(starts, index) 1222 sizes = _convert_to_series_and_validate_index(sizes, index) 1223 interval_array = [] 1224 for i in index: 1225 interval_array.append( 1226 self.new_fixed_size_interval_var( 1227 start=starts[i], 1228 size=sizes[i], 1229 name=f"{name}[{i}]", 1230 ) 1231 ) 1232 return pd.Series(index=index, data=interval_array) 1233 1234 def new_optional_interval_var( 1235 self, 1236 start: LinearExprT, 1237 size: LinearExprT, 1238 end: LinearExprT, 1239 is_present: LiteralT, 1240 name: str, 1241 ) -> IntervalVar: 1242 """Creates an optional interval var from start, size, end, and is_present. 1243 1244 An optional interval variable is a constraint, that is itself used in other 1245 constraints like NoOverlap. This constraint is protected by a presence 1246 literal that indicates if it is active or not. 1247 1248 Internally, it ensures that `is_present` implies `start + size == 1249 end`. 1250 1251 Args: 1252 start: The start of the interval. It must be of the form a * var + b. 1253 size: The size of the interval. It must be of the form a * var + b. 1254 end: The end of the interval. It must be of the form a * var + b. 1255 is_present: A literal that indicates if the interval is active or not. A 1256 inactive interval is simply ignored by all constraints. 1257 name: The name of the interval variable. 1258 1259 Returns: 1260 An `IntervalVar` object. 1261 """ 1262 return self._new_interval_var( 1263 name, 1264 start, 1265 size, 1266 end, 1267 [is_present], 1268 ) 1269 1270 def new_optional_interval_var_series( 1271 self, 1272 name: str, 1273 index: pd.Index, 1274 starts: Union[LinearExprT, pd.Series], 1275 sizes: Union[LinearExprT, pd.Series], 1276 ends: Union[LinearExprT, pd.Series], 1277 are_present: Union[LiteralT, pd.Series], 1278 ) -> pd.Series: 1279 """Creates a series of interval variables with the given name. 1280 1281 Args: 1282 name (str): Required. The name of the variable set. 1283 index (pd.Index): Required. The index to use for the variable set. 1284 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1285 set. If a `pd.Series` is passed in, it will be based on the 1286 corresponding values of the pd.Series. 1287 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the 1288 set. If a `pd.Series` is passed in, it will be based on the 1289 corresponding values of the pd.Series. 1290 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the 1291 set. If a `pd.Series` is passed in, it will be based on the 1292 corresponding values of the pd.Series. 1293 are_present (Union[LiteralT, pd.Series]): The performed literal of each 1294 interval in the set. If a `pd.Series` is passed in, it will be based on 1295 the corresponding values of the pd.Series. 1296 1297 Returns: 1298 pd.Series: The interval variable set indexed by its corresponding 1299 dimensions. 1300 1301 Raises: 1302 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1303 ValueError: if the `name` is not a valid identifier or already exists. 1304 ValueError: if the all the indexes do not match. 1305 """ 1306 if not isinstance(index, pd.Index): 1307 raise TypeError("Non-index object is used as index") 1308 if not name.isidentifier(): 1309 raise ValueError(f"name={name!r} is not a valid identifier") 1310 1311 starts = _convert_to_series_and_validate_index(starts, index) 1312 sizes = _convert_to_series_and_validate_index(sizes, index) 1313 ends = _convert_to_series_and_validate_index(ends, index) 1314 are_present = _convert_to_series_and_validate_index(are_present, index) 1315 1316 interval_array = [] 1317 for i in index: 1318 interval_array.append( 1319 self.new_optional_interval_var( 1320 start=starts[i], 1321 size=sizes[i], 1322 end=ends[i], 1323 is_present=are_present[i], 1324 name=f"{name}[{i}]", 1325 ) 1326 ) 1327 return pd.Series(index=index, data=interval_array) 1328 1329 def new_optional_fixed_size_interval_var( 1330 self, 1331 start: LinearExprT, 1332 size: IntegralT, 1333 is_present: LiteralT, 1334 name: str, 1335 ) -> IntervalVar: 1336 """Creates an interval variable from start, and a fixed size. 1337 1338 An interval variable is a constraint, that is itself used in other 1339 constraints like NoOverlap. 1340 1341 Args: 1342 start: The start of the interval. It must be of the form a * var + b. 1343 size: The size of the interval. It must be an integer value. 1344 is_present: A literal that indicates if the interval is active or not. A 1345 inactive interval is simply ignored by all constraints. 1346 name: The name of the interval variable. 1347 1348 Returns: 1349 An `IntervalVar` object. 1350 """ 1351 return self._new_interval_var( 1352 name, 1353 start, 1354 size, 1355 start + size, 1356 [is_present], 1357 ) 1358 1359 def new_optional_fixed_size_interval_var_series( 1360 self, 1361 name: str, 1362 index: pd.Index, 1363 starts: Union[LinearExprT, pd.Series], 1364 sizes: Union[IntegralT, pd.Series], 1365 are_present: Union[LiteralT, pd.Series], 1366 ) -> pd.Series: 1367 """Creates a series of interval variables with the given name. 1368 1369 Args: 1370 name (str): Required. The name of the variable set. 1371 index (pd.Index): Required. The index to use for the variable set. 1372 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1373 set. If a `pd.Series` is passed in, it will be based on the 1374 corresponding values of the pd.Series. 1375 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in 1376 the set. If a `pd.Series` is passed in, it will be based on the 1377 corresponding values of the pd.Series. 1378 are_present (Union[LiteralT, pd.Series]): The performed literal of each 1379 interval in the set. If a `pd.Series` is passed in, it will be based on 1380 the corresponding values of the pd.Series. 1381 1382 Returns: 1383 pd.Series: The interval variable set indexed by its corresponding 1384 dimensions. 1385 1386 Raises: 1387 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1388 ValueError: if the `name` is not a valid identifier or already exists. 1389 ValueError: if the all the indexes do not match. 1390 """ 1391 if not isinstance(index, pd.Index): 1392 raise TypeError("Non-index object is used as index") 1393 if not name.isidentifier(): 1394 raise ValueError(f"name={name!r} is not a valid identifier") 1395 1396 starts = _convert_to_series_and_validate_index(starts, index) 1397 sizes = _convert_to_series_and_validate_index(sizes, index) 1398 are_present = _convert_to_series_and_validate_index(are_present, index) 1399 interval_array = [] 1400 for i in index: 1401 interval_array.append( 1402 self.new_optional_fixed_size_interval_var( 1403 start=starts[i], 1404 size=sizes[i], 1405 is_present=are_present[i], 1406 name=f"{name}[{i}]", 1407 ) 1408 ) 1409 return pd.Series(index=index, data=interval_array) 1410 1411 def add_no_overlap(self, intervals: Iterable[IntervalVar]) -> Constraint: 1412 """Adds NoOverlap(interval_vars). 1413 1414 A NoOverlap constraint ensures that all present intervals do not overlap 1415 in time. 1416 1417 Args: 1418 intervals: The list of interval variables to constrain. 1419 1420 Returns: 1421 An instance of the `Constraint` class. 1422 """ 1423 return self._add_no_overlap(intervals) 1424 1425 def add_no_overlap_2d( 1426 self, 1427 x_intervals: Iterable[IntervalVar], 1428 y_intervals: Iterable[IntervalVar], 1429 ) -> Constraint: 1430 """Adds NoOverlap2D(x_intervals, y_intervals). 1431 1432 A NoOverlap2D constraint ensures that all present rectangles do not overlap 1433 on a plane. Each rectangle is aligned with the X and Y axis, and is defined 1434 by two intervals which represent its projection onto the X and Y axis. 1435 1436 Furthermore, one box is optional if at least one of the x or y interval is 1437 optional. 1438 1439 Args: 1440 x_intervals: The X coordinates of the rectangles. 1441 y_intervals: The Y coordinates of the rectangles. 1442 1443 Returns: 1444 An instance of the `Constraint` class. 1445 """ 1446 return self._add_no_overlap_2d(x_intervals, y_intervals) 1447 1448 def add_cumulative( 1449 self, 1450 intervals: Iterable[IntervalVar], 1451 demands: Iterable[LinearExprT], 1452 capacity: LinearExprT, 1453 ) -> Constraint: 1454 """Adds Cumulative(intervals, demands, capacity). 1455 1456 This constraint enforces that: 1457 1458 for all t: 1459 sum(demands[i] 1460 if (start(intervals[i]) <= t < end(intervals[i])) and 1461 (intervals[i] is present)) <= capacity 1462 1463 Args: 1464 intervals: The list of intervals. 1465 demands: The list of demands for each interval. Each demand must be >= 0. 1466 Each demand can be a 1-var affine expression (a * x + b). 1467 capacity: The maximum capacity of the cumulative constraint. It can be a 1468 1-var affine expression (a * x + b). 1469 1470 Returns: 1471 An instance of the `Constraint` class. 1472 """ 1473 return self._add_cumulative(intervals, demands, capacity) 1474 1475 # Support for model cloning. 1476 def clone(self) -> "CpModel": 1477 """Reset the model, and creates a new one from a CpModelProto instance.""" 1478 clone = CpModel() 1479 clone.proto.copy_from(self.proto) 1480 clone.rebuild_constant_map() 1481 return clone 1482 1483 def __copy__(self): 1484 return CpModel(self.model_proto) 1485 1486 def __deepcopy__(self, memo): 1487 return CpModel(copy.deepcopy(self.model_proto, memo)) 1488 1489 def get_bool_var_from_proto_index(self, index: int) -> IntVar: 1490 """Returns an already created Boolean variable from its index.""" 1491 if index < 0 or index >= len(self.model_proto.variables): 1492 raise ValueError( 1493 f"get_bool_var_from_proto_index: out of bound index {index}" 1494 ) 1495 result = IntVar(self.model_proto, index) 1496 if not result.is_boolean: 1497 raise TypeError( 1498 f"get_bool_var_from_proto_index: index {index} does not reference a" 1499 " boolean variable" 1500 ) 1501 return result 1502 1503 def get_int_var_from_proto_index(self, index: int) -> IntVar: 1504 """Returns an already created integer variable from its index.""" 1505 if index < 0 or index >= len(self.model_proto.variables): 1506 raise ValueError( 1507 f"get_int_var_from_proto_index: out of bound index {index}" 1508 ) 1509 return IntVar(self.model_proto, index) 1510 1511 def get_interval_var_from_proto_index(self, index: int) -> IntervalVar: 1512 """Returns an already created interval variable from its index.""" 1513 if index < 0 or index >= len(self.model_proto.constraints): 1514 raise ValueError( 1515 f"get_interval_var_from_proto_index: out of bound index {index}" 1516 ) 1517 ct = self.model_proto.constraints[index] 1518 if not ct.has_interval(): 1519 raise ValueError( 1520 f"get_interval_var_from_proto_index: index {index} does not" 1521 " reference an" + " interval variable" 1522 ) 1523 1524 return IntervalVar(self.model_proto, index) 1525 1526 def __str__(self) -> str: 1527 return str(self.model_proto) 1528 1529 @property 1530 def proto(self) -> cmh.CpModelProto: 1531 """Returns the underlying CpModelProto.""" 1532 return self.model_proto 1533 1534 def negated(self, index: int) -> int: 1535 return -index - 1 1536 1537 def _set_objective(self, obj: ObjLinearExprT, maximize: bool): 1538 """Sets the objective of the model.""" 1539 self.clear_objective() 1540 if isinstance(obj, IntegralTypes): 1541 self.model_proto.objective.offset = int(obj) 1542 self.model_proto.objective.scaling_factor = 1.0 1543 elif isinstance(obj, LinearExpr): 1544 if obj.is_integer(): 1545 int_obj = cmh.FlatIntExpr(obj) 1546 for var in int_obj.vars: 1547 self.model_proto.objective.vars.append(var.index) 1548 if maximize: 1549 self.model_proto.objective.scaling_factor = -1.0 1550 self.model_proto.objective.offset = -int_obj.offset 1551 for c in int_obj.coeffs: 1552 self.model_proto.objective.coeffs.append(-c) 1553 else: 1554 self.model_proto.objective.scaling_factor = 1.0 1555 self.model_proto.objective.offset = int_obj.offset 1556 self.model_proto.objective.coeffs.extend(int_obj.coeffs) 1557 else: 1558 float_obj = cmh.FlatFloatExpr(obj) 1559 for var in float_obj.vars: 1560 self.model_proto.floating_point_objective.vars.append(var.index) 1561 self.model_proto.floating_point_objective.coeffs.extend( 1562 float_obj.coeffs 1563 ) 1564 self.model_proto.floating_point_objective.maximize = maximize 1565 self.model_proto.floating_point_objective.offset = float_obj.offset 1566 else: 1567 raise TypeError( 1568 f"TypeError: {type(obj).__name__!r} is not a valid objective" 1569 ) 1570 1571 def minimize(self, obj: ObjLinearExprT): 1572 """Sets the objective of the model to minimize(obj).""" 1573 self._set_objective(obj, maximize=False) 1574 1575 def maximize(self, obj: ObjLinearExprT): 1576 """Sets the objective of the model to maximize(obj).""" 1577 self._set_objective(obj, maximize=True) 1578 1579 def has_objective(self) -> bool: 1580 return ( 1581 self.model_proto.has_objective() 1582 or self.model_proto.has_floating_point_objective() 1583 ) 1584 1585 def clear_objective(self): 1586 self.model_proto.clear_objective() 1587 self.model_proto.clear_floating_point_objective() 1588 1589 def add_decision_strategy( 1590 self, 1591 variables: Iterable[IntVar], 1592 var_strategy: cmh.DecisionStrategyProto.VariableSelectionStrategy, 1593 domain_strategy: cmh.DecisionStrategyProto.DomainReductionStrategy, 1594 ) -> None: 1595 """Adds a search strategy to the model. 1596 1597 Args: 1598 variables: a list of variables this strategy will assign. 1599 var_strategy: heuristic to choose the next variable to assign. 1600 domain_strategy: heuristic to reduce the domain of the selected variable. 1601 Currently, this is advanced code: the union of all strategies added to 1602 the model must be complete, i.e. instantiates all variables. Otherwise, 1603 solve() will fail. 1604 """ 1605 1606 strategy: cmh.DecisionStrategyProto = self.model_proto.search_strategy.add() 1607 for v in variables: 1608 expr = strategy.exprs.add() 1609 if v.index >= 0: 1610 expr.vars.append(v.index) 1611 expr.coeffs.append(1) 1612 else: 1613 expr.vars.append(self.negated(v.index)) 1614 expr.coeffs.append(-1) 1615 expr.offset = 1 1616 1617 strategy.variable_selection_strategy = var_strategy 1618 strategy.domain_reduction_strategy = domain_strategy 1619 1620 def model_stats(self) -> str: 1621 """Returns a string containing some model statistics.""" 1622 return cmh.CpSatHelper.model_stats(self.model_proto) 1623 1624 def validate(self) -> str: 1625 """Returns a string indicating that the model is invalid.""" 1626 return cmh.CpSatHelper.validate_model(self.model_proto) 1627 1628 def export_to_file(self, file: str) -> bool: 1629 """Write the model as a protocol buffer to 'file'. 1630 1631 Args: 1632 file: file to write the model to. If the filename ends with 'txt', the 1633 model will be written as a text file, otherwise, the binary format will 1634 be used. 1635 1636 Returns: 1637 True if the model was correctly written. 1638 """ 1639 return cmh.CpSatHelper.write_model_to_file(self.model_proto, file) 1640 1641 def remove_all_names(self) -> None: 1642 """Removes all names from the model.""" 1643 self.model_proto.clear_name() 1644 for v in self.model_proto.variables: 1645 v.clear_name() 1646 for c in self.model_proto.constraints: 1647 c.clear_name() 1648 1649 @overload 1650 def add_hint(self, var: IntVar, value: int) -> None: ... 1651 1652 @overload 1653 def add_hint(self, literal: BoolVarT, value: bool) -> None: ... 1654 1655 def add_hint(self, var, value) -> None: 1656 """Adds 'var == value' as a hint to the solver.""" 1657 if var.index >= 0: 1658 self.model_proto.solution_hint.vars.append(var.index) 1659 self.model_proto.solution_hint.values.append(int(value)) 1660 else: 1661 self.model_proto.solution_hint.vars.append(self.negated(var.index)) 1662 self.model_proto.solution_hint.values.append(int(not value)) 1663 1664 def clear_hints(self): 1665 """Removes any solution hint from the model.""" 1666 self.model_proto.clear_solution_hint() 1667 1668 def add_assumption(self, lit: LiteralT) -> None: 1669 """Adds the literal to the model as assumptions.""" 1670 self.model_proto.assumptions.append(self.get_or_make_boolean_index(lit)) 1671 1672 def add_assumptions(self, literals: Iterable[LiteralT]) -> None: 1673 """Adds the literals to the model as assumptions.""" 1674 for lit in literals: 1675 self.add_assumption(lit) 1676 1677 def clear_assumptions(self) -> None: 1678 """Removes all assumptions from the model.""" 1679 self.model_proto.assumptions.clear() 1680 1681 # Compatibility with pre PEP8 1682 # pylint: disable=invalid-name 1683 1684 def _add_pre_pep8_methods(self) -> None: 1685 for method_name in dir(self): 1686 if callable(getattr(self, method_name)) and ( 1687 method_name.startswith("add_") 1688 or method_name.startswith("new_") 1689 or method_name.startswith("clear_") 1690 ): 1691 pre_pep8_name = snake_case_to_camel_case(method_name) 1692 setattr( 1693 self, 1694 pre_pep8_name, 1695 deprecated_method(getattr(self, method_name), pre_pep8_name), 1696 ) 1697 1698 for other_method_name in [ 1699 "add", 1700 "clone", 1701 "get_bool_var_from_proto_index", 1702 "get_int_var_from_proto_index", 1703 "get_interval_var_from_proto_index", 1704 "minimize", 1705 "maximize", 1706 "has_objective", 1707 "model_stats", 1708 "validate", 1709 "export_to_file", 1710 ]: 1711 pre_pep8_name = snake_case_to_camel_case(other_method_name) 1712 setattr( 1713 self, 1714 pre_pep8_name, 1715 deprecated_method(getattr(self, other_method_name), pre_pep8_name), 1716 ) 1717 1718 @deprecated("Use name property instead.") 1719 def Name(self) -> str: 1720 return self.name 1721 1722 @deprecated("Use name property instead.") 1723 def SetName(self, name: str) -> None: 1724 self.name = name 1725 1726 @deprecated("Use proto property instead.") 1727 def Proto(self) -> cmh.CpModelProto: 1728 return self.proto 1729 1730 # pylint: enable=invalid-name 1731 1732 1733class CpSolver: 1734 """Main solver class. 1735 1736 The purpose of this class is to search for a solution to the model provided 1737 to the solve() method. 1738 1739 Once solve() is called, this class allows inspecting the solution found 1740 with the value() and boolean_value() methods, as well as general statistics 1741 about the solve procedure. 1742 """ 1743 1744 def __init__(self) -> None: 1745 self.__response: Optional[cmh.CpSolverResponse] = None 1746 self.parameters: cmh.SatParameters = cmh.SatParameters() 1747 self.log_callback: Optional[Callable[[str], None]] = None 1748 self.best_bound_callback: Optional[Callable[[float], None]] = None 1749 self.__solve_wrapper: Optional[cmh.SolveWrapper] = None 1750 self.__lock: threading.Lock = threading.Lock() 1751 1752 def solve( 1753 self, 1754 model: CpModel, 1755 solution_callback: Optional["CpSolverSolutionCallback"] = None, 1756 ) -> cmh.CpSolverStatus: 1757 """Solves a problem and passes each solution to the callback if not null.""" 1758 with self.__lock: 1759 self.__solve_wrapper = cmh.SolveWrapper() 1760 1761 self.__solve_wrapper.set_parameters(self.parameters) 1762 if solution_callback is not None: 1763 self.__solve_wrapper.add_solution_callback(solution_callback) 1764 1765 if self.log_callback is not None: 1766 self.__solve_wrapper.add_log_callback(self.log_callback) 1767 1768 if self.best_bound_callback is not None: 1769 self.__solve_wrapper.add_best_bound_callback(self.best_bound_callback) 1770 1771 self.__response = self.__solve_wrapper.solve(model.proto) 1772 1773 if solution_callback is not None: 1774 self.__solve_wrapper.clear_solution_callback(solution_callback) 1775 1776 with self.__lock: 1777 self.__solve_wrapper = None 1778 1779 return self.__response.status 1780 1781 def stop_search(self) -> None: 1782 """Stops the current search asynchronously.""" 1783 with self.__lock: 1784 if self.__solve_wrapper: 1785 self.__solve_wrapper.stop_search() 1786 1787 def value(self, expression: LinearExprT) -> int: 1788 """Returns the value of a linear expression after solve.""" 1789 return cmh.ResponseHelper.value(self._checked_response, expression) 1790 1791 def values(self, variables: _IndexOrSeries) -> pd.Series: 1792 """Returns the values of the input variables. 1793 1794 If `variables` is a `pd.Index`, then the output will be indexed by the 1795 variables. If `variables` is a `pd.Series` indexed by the underlying 1796 dimensions, then the output will be indexed by the same underlying 1797 dimensions. 1798 1799 Args: 1800 variables (Union[pd.Index, pd.Series]): The set of variables from which to 1801 get the values. 1802 1803 Returns: 1804 pd.Series: The values of all variables in the set. 1805 1806 Raises: 1807 RuntimeError: if solve() has not been called. 1808 """ 1809 response: cmh.CpSolverResponse = self._checked_response 1810 return pd.Series( 1811 data=[cmh.ResponseHelper.value(response, var) for var in variables], 1812 index=_get_index(variables), 1813 ) 1814 1815 def float_value(self, expression: LinearExprT) -> float: 1816 """Returns the value of a linear expression after solve.""" 1817 return cmh.ResponseHelper.float_value(self._checked_response, expression) 1818 1819 def float_values(self, expressions: _IndexOrSeries) -> pd.Series: 1820 """Returns the float values of the input linear expressions. 1821 1822 If `expressions` is a `pd.Index`, then the output will be indexed by the 1823 variables. If `variables` is a `pd.Series` indexed by the underlying 1824 dimensions, then the output will be indexed by the same underlying 1825 dimensions. 1826 1827 Args: 1828 expressions (Union[pd.Index, pd.Series]): The set of expressions from 1829 which to get the values. 1830 1831 Returns: 1832 pd.Series: The values of all variables in the set. 1833 1834 Raises: 1835 RuntimeError: if solve() has not been called. 1836 """ 1837 response: cmh.CpSolverResponse = self._checked_response 1838 return pd.Series( 1839 data=[ 1840 cmh.ResponseHelper.float_value(response, expr) for expr in expressions 1841 ], 1842 index=_get_index(expressions), 1843 ) 1844 1845 def boolean_value(self, literal: LiteralT) -> bool: 1846 """Returns the boolean value of a literal after solve.""" 1847 return cmh.ResponseHelper.boolean_value(self._checked_response, literal) 1848 1849 def boolean_values(self, variables: _IndexOrSeries) -> pd.Series: 1850 """Returns the values of the input variables. 1851 1852 If `variables` is a `pd.Index`, then the output will be indexed by the 1853 variables. If `variables` is a `pd.Series` indexed by the underlying 1854 dimensions, then the output will be indexed by the same underlying 1855 dimensions. 1856 1857 Args: 1858 variables (Union[pd.Index, pd.Series]): The set of variables from which to 1859 get the values. 1860 1861 Returns: 1862 pd.Series: The values of all variables in the set. 1863 1864 Raises: 1865 RuntimeError: if solve() has not been called. 1866 """ 1867 response: cmh.CpSolverResponse = self._checked_response 1868 return pd.Series( 1869 data=[ 1870 cmh.ResponseHelper.boolean_value(response, literal) 1871 for literal in variables 1872 ], 1873 index=_get_index(variables), 1874 ) 1875 1876 @property 1877 def objective_value(self) -> float: 1878 """Returns the value of the objective after solve.""" 1879 return self._checked_response.objective_value 1880 1881 @property 1882 def best_objective_bound(self) -> float: 1883 """Returns the best lower (upper) bound found when min(max)imizing.""" 1884 return self._checked_response.best_objective_bound 1885 1886 @property 1887 def num_booleans(self) -> int: 1888 """Returns the number of boolean variables managed by the SAT solver.""" 1889 return self._checked_response.num_booleans 1890 1891 @property 1892 def num_conflicts(self) -> int: 1893 """Returns the number of conflicts since the creation of the solver.""" 1894 return self._checked_response.num_conflicts 1895 1896 @property 1897 def num_branches(self) -> int: 1898 """Returns the number of search branches explored by the solver.""" 1899 return self._checked_response.num_branches 1900 1901 @property 1902 def num_binary_propagations(self) -> int: 1903 """Returns the number of Boolean propagations done by the solver.""" 1904 return self._checked_response.num_binary_propagations 1905 1906 @property 1907 def num_integer_propagations(self) -> int: 1908 """Returns the number of integer propagations done by the solver.""" 1909 return self._checked_response.num_integer_propagations 1910 1911 @property 1912 def deterministic_time(self) -> float: 1913 """Returns the deterministic time in seconds since the creation of the solver.""" 1914 return self._checked_response.deterministic_time 1915 1916 @property 1917 def wall_time(self) -> float: 1918 """Returns the wall time in seconds since the creation of the solver.""" 1919 return self._checked_response.wall_time 1920 1921 @property 1922 def user_time(self) -> float: 1923 """Returns the user time in seconds since the creation of the solver.""" 1924 return self._checked_response.user_time 1925 1926 @property 1927 def solve_log(self) -> str: 1928 """Returns the solve log. 1929 1930 To enable this, the parameter log_to_response must be set to True. 1931 """ 1932 return self._checked_response.solve_log 1933 1934 @property 1935 def solve_info(self) -> str: 1936 """Returns the information about the solve.""" 1937 return self._checked_response.solve_info 1938 1939 @property 1940 def response_proto(self) -> cmh.CpSolverResponse: 1941 """Returns the response object.""" 1942 return self._checked_response 1943 1944 def response_stats(self) -> str: 1945 """Returns some statistics on the solution found as a string.""" 1946 return cmh.CpSatHelper.solver_response_stats(self._checked_response) 1947 1948 def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]: 1949 """Returns the indices of the infeasible assumptions.""" 1950 return cmh.ResponseHelper.sufficient_assumptions_for_infeasibility( 1951 self._checked_response 1952 ) 1953 1954 def status_name(self, status: Optional[Any] = None) -> str: 1955 """Returns the name of the status returned by solve().""" 1956 if status is None: 1957 status = self._checked_response.status() 1958 return status.name 1959 1960 def solution_info(self) -> str: 1961 """Returns some information on the solve process. 1962 1963 Returns some information on how the solution was found, or the reason 1964 why the model or the parameters are invalid. 1965 1966 Raises: 1967 RuntimeError: if solve() has not been called. 1968 """ 1969 return self._checked_response.solution_info 1970 1971 @property 1972 def _checked_response(self) -> cmh.CpSolverResponse: 1973 """Checks solve() has been called, and returns a response wrapper.""" 1974 if self.__response is None: 1975 raise RuntimeError("solve() has not been called.") 1976 return self.__response 1977 1978 # Compatibility with pre PEP8 1979 # pylint: disable=invalid-name 1980 1981 @deprecated("Use best_objective_bound property instead.") 1982 def BestObjectiveBound(self) -> float: 1983 return self.best_objective_bound 1984 1985 @deprecated("Use boolean_value() method instead.") 1986 def BooleanValue(self, lit: LiteralT) -> bool: 1987 return self.boolean_value(lit) 1988 1989 @deprecated("Use boolean_values() method instead.") 1990 def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series: 1991 return self.boolean_values(variables) 1992 1993 @deprecated("Use num_booleans property instead.") 1994 def NumBooleans(self) -> int: 1995 return self.num_booleans 1996 1997 @deprecated("Use num_conflicts property instead.") 1998 def NumConflicts(self) -> int: 1999 return self.num_conflicts 2000 2001 @deprecated("Use num_branches property instead.") 2002 def NumBranches(self) -> int: 2003 return self.num_branches 2004 2005 @deprecated("Use objective_value property instead.") 2006 def ObjectiveValue(self) -> float: 2007 return self.objective_value 2008 2009 @deprecated("Use response_proto property instead.") 2010 def ResponseProto(self) -> cmh.CpSolverResponse: 2011 return self.response_proto 2012 2013 @deprecated("Use response_stats() method instead.") 2014 def ResponseStats(self) -> str: 2015 return self.response_stats() 2016 2017 @deprecated("Use solve() method instead.") 2018 def Solve( 2019 self, model: CpModel, callback: "CpSolverSolutionCallback" = None 2020 ) -> cmh.CpSolverStatus: 2021 return self.solve(model, callback) 2022 2023 @deprecated("Use solution_info() method instead.") 2024 def SolutionInfo(self) -> str: 2025 return self.solution_info() 2026 2027 @deprecated("Use status_name() method instead.") 2028 def StatusName(self, status: Optional[Any] = None) -> str: 2029 return self.status_name(status) 2030 2031 @deprecated("Use stop_search() method instead.") 2032 def StopSearch(self) -> None: 2033 self.stop_search() 2034 2035 @deprecated("Use sufficient_assumptions_for_infeasibility() method instead.") 2036 def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]: 2037 return self.sufficient_assumptions_for_infeasibility() 2038 2039 @deprecated("Use user_time property instead.") 2040 def UserTime(self) -> float: 2041 return self.user_time 2042 2043 @deprecated("Use value() method instead.") 2044 def Value(self, expression: LinearExprT) -> int: 2045 return self.value(expression) 2046 2047 @deprecated("Use values() method instead.") 2048 def Values(self, expressions: _IndexOrSeries) -> pd.Series: 2049 return self.values(expressions) 2050 2051 @deprecated("Use wall_time property instead.") 2052 def WallTime(self) -> float: 2053 return self.wall_time 2054 2055 @deprecated("Use solve() with enumerate_all_solutions = True.") 2056 def SearchForAllSolutions( 2057 self, model: CpModel, callback: "CpSolverSolutionCallback" 2058 ) -> cmh.CpSolverStatus: 2059 """Search for all solutions of a satisfiability problem. 2060 2061 This method searches for all feasible solutions of a given model. 2062 Then it feeds the solution to the callback. 2063 2064 Note that the model cannot contain an objective. 2065 2066 Args: 2067 model: The model to solve. 2068 callback: The callback that will be called at each solution. 2069 2070 Returns: 2071 The status of the solve: 2072 2073 * *FEASIBLE* if some solutions have been found 2074 * *INFEASIBLE* if the solver has proved there are no solution 2075 * *OPTIMAL* if all solutions have been found 2076 """ 2077 if model.has_objective(): 2078 raise TypeError( 2079 "Search for all solutions is only defined on satisfiability problems" 2080 ) 2081 # Store old parameter. 2082 enumerate_all = self.parameters.enumerate_all_solutions 2083 self.parameters.enumerate_all_solutions = True 2084 2085 status: cmh.CpSolverStatus = self.solve(model, callback) 2086 2087 # Restore parameter. 2088 self.parameters.enumerate_all_solutions = enumerate_all 2089 return status 2090 2091 2092# pylint: enable=invalid-name 2093 2094 2095class CpSolverSolutionCallback(cmh.SolutionCallback): 2096 """Solution callback. 2097 2098 This class implements a callback that will be called at each new solution 2099 found during search. 2100 2101 The method on_solution_callback() will be called by the solver, and must be 2102 implemented. The current solution can be queried using the boolean_value() 2103 and value() methods. 2104 2105 These methods returns the same information as their counterpart in the 2106 `CpSolver` class. 2107 """ 2108 2109 def __init__(self) -> None: 2110 cmh.SolutionCallback.__init__(self) 2111 2112 # pylint: disable=invalid-name 2113 def OnSolutionCallback(self) -> None: 2114 """Proxy for the same method in snake case.""" 2115 self.on_solution_callback() 2116 2117 # pylint: enable=invalid-name 2118 2119 def boolean_value(self, lit: LiteralT) -> bool: 2120 """Returns the boolean value of a boolean literal. 2121 2122 Args: 2123 lit: A boolean variable or its negation. 2124 2125 Returns: 2126 The Boolean value of the literal in the solution. 2127 2128 Raises: 2129 RuntimeError: if `lit` is not a boolean variable or its negation. 2130 """ 2131 if not self.has_response(): 2132 raise RuntimeError("solve() has not been called.") 2133 return self.BooleanValue(lit) 2134 2135 def value(self, expression: LinearExprT) -> int: 2136 """Evaluates an linear expression in the current solution. 2137 2138 Args: 2139 expression: a linear expression of the model. 2140 2141 Returns: 2142 An integer value equal to the evaluation of the linear expression 2143 against the current solution. 2144 2145 Raises: 2146 RuntimeError: if 'expression' is not a LinearExpr. 2147 """ 2148 if not self.has_response(): 2149 raise RuntimeError("solve() has not been called.") 2150 return self.Value(expression) 2151 2152 def float_value(self, expression: LinearExprT) -> float: 2153 """Evaluates an linear expression in the current solution. 2154 2155 Args: 2156 expression: a linear expression of the model. 2157 2158 Returns: 2159 An integer value equal to the evaluation of the linear expression 2160 against the current solution. 2161 2162 Raises: 2163 RuntimeError: if 'expression' is not a LinearExpr. 2164 """ 2165 if not self.has_response(): 2166 raise RuntimeError("solve() has not been called.") 2167 return self.FloatValue(expression) 2168 2169 def has_response(self) -> bool: 2170 return self.HasResponse() 2171 2172 def stop_search(self) -> None: 2173 """Stops the current search asynchronously.""" 2174 if not self.has_response(): 2175 raise RuntimeError("solve() has not been called.") 2176 self.StopSearch() 2177 2178 @property 2179 def objective_value(self) -> float: 2180 """Returns the value of the objective after solve.""" 2181 if not self.has_response(): 2182 raise RuntimeError("solve() has not been called.") 2183 return self.ObjectiveValue() 2184 2185 @property 2186 def best_objective_bound(self) -> float: 2187 """Returns the best lower (upper) bound found when min(max)imizing.""" 2188 if not self.has_response(): 2189 raise RuntimeError("solve() has not been called.") 2190 return self.BestObjectiveBound() 2191 2192 @property 2193 def num_booleans(self) -> int: 2194 """Returns the number of boolean variables managed by the SAT solver.""" 2195 if not self.has_response(): 2196 raise RuntimeError("solve() has not been called.") 2197 return self.NumBooleans() 2198 2199 @property 2200 def num_conflicts(self) -> int: 2201 """Returns the number of conflicts since the creation of the solver.""" 2202 if not self.has_response(): 2203 raise RuntimeError("solve() has not been called.") 2204 return self.NumConflicts() 2205 2206 @property 2207 def num_branches(self) -> int: 2208 """Returns the number of search branches explored by the solver.""" 2209 if not self.has_response(): 2210 raise RuntimeError("solve() has not been called.") 2211 return self.NumBranches() 2212 2213 @property 2214 def num_integer_propagations(self) -> int: 2215 """Returns the number of integer propagations done by the solver.""" 2216 if not self.has_response(): 2217 raise RuntimeError("solve() has not been called.") 2218 return self.NumIntegerPropagations() 2219 2220 @property 2221 def num_binary_propagations(self) -> int: 2222 """Returns the number of Boolean propagations done by the solver.""" 2223 if not self.has_response(): 2224 raise RuntimeError("solve() has not been called.") 2225 return self.NumBinaryPropagations() 2226 2227 @property 2228 def deterministic_time(self) -> float: 2229 """Returns the determistic time in seconds since the creation of the solver.""" 2230 if not self.has_response(): 2231 raise RuntimeError("solve() has not been called.") 2232 return self.DeterministicTime() 2233 2234 @property 2235 def wall_time(self) -> float: 2236 """Returns the wall time in seconds since the creation of the solver.""" 2237 if not self.has_response(): 2238 raise RuntimeError("solve() has not been called.") 2239 return self.WallTime() 2240 2241 @property 2242 def user_time(self) -> float: 2243 """Returns the user time in seconds since the creation of the solver.""" 2244 if not self.has_response(): 2245 raise RuntimeError("solve() has not been called.") 2246 return self.UserTime() 2247 2248 @property 2249 def response_proto(self) -> cmh.CpSolverResponse: 2250 """Returns the response object.""" 2251 if not self.has_response(): 2252 raise RuntimeError("solve() has not been called.") 2253 return self.Response() 2254 2255 2256class ObjectiveSolutionPrinter(CpSolverSolutionCallback): 2257 """Display the objective value and time of intermediate solutions.""" 2258 2259 def __init__(self) -> None: 2260 CpSolverSolutionCallback.__init__(self) 2261 self.__solution_count = 0 2262 self.__start_time = time.time() 2263 2264 def on_solution_callback(self) -> None: 2265 """Called on each new solution.""" 2266 current_time = time.time() 2267 obj = self.objective_value 2268 print( 2269 f"Solution {self.__solution_count}, time =" 2270 f" {current_time - self.__start_time:0.2f} s, objective = {obj}", 2271 flush=True, 2272 ) 2273 self.__solution_count += 1 2274 2275 def solution_count(self) -> int: 2276 """Returns the number of solutions found.""" 2277 return self.__solution_count 2278 2279 2280class VarArrayAndObjectiveSolutionPrinter(CpSolverSolutionCallback): 2281 """Print intermediate solutions (objective, variable values, time).""" 2282 2283 def __init__(self, variables: Sequence[IntVar]) -> None: 2284 CpSolverSolutionCallback.__init__(self) 2285 self.__variables: Sequence[IntVar] = variables 2286 self.__solution_count: int = 0 2287 self.__start_time: float = time.time() 2288 2289 def on_solution_callback(self) -> None: 2290 """Called on each new solution.""" 2291 current_time = time.time() 2292 obj = self.objective_value 2293 print( 2294 f"Solution {self.__solution_count}, time =" 2295 f" {current_time - self.__start_time:0.2f} s, objective = {obj}" 2296 ) 2297 for v in self.__variables: 2298 print(f" {v} = {self.value(v)}", end=" ") 2299 print(flush=True) 2300 self.__solution_count += 1 2301 2302 @property 2303 def solution_count(self) -> int: 2304 """Returns the number of solutions found.""" 2305 return self.__solution_count 2306 2307 2308class VarArraySolutionPrinter(CpSolverSolutionCallback): 2309 """Print intermediate solutions (variable values, time).""" 2310 2311 def __init__(self, variables: Sequence[IntVar]) -> None: 2312 CpSolverSolutionCallback.__init__(self) 2313 self.__variables: Sequence[IntVar] = variables 2314 self.__solution_count: int = 0 2315 self.__start_time: float = time.time() 2316 2317 def on_solution_callback(self) -> None: 2318 """Called on each new solution.""" 2319 current_time = time.time() 2320 print( 2321 f"Solution {self.__solution_count}, time =" 2322 f" {current_time - self.__start_time:0.2f} s" 2323 ) 2324 for v in self.__variables: 2325 print(f" {v} = {self.value(v)}", end=" ") 2326 print(flush=True) 2327 self.__solution_count += 1 2328 2329 @property 2330 def solution_count(self) -> int: 2331 """Returns the number of solutions found.""" 2332 return self.__solution_count
A class to hold a linear expression with bounds.
__init__(args, *kwargs) Overloaded function.
__init__(self: ortools.sat.python.cp_model_helper.BoundedLinearExpression, arg0: ortools.sat.python.cp_model_helper.LinearExpr, arg1: ortools.util.python.sorted_interval_list.Domain) -> None
__init__(self: ortools.sat.python.cp_model_helper.BoundedLinearExpression, arg0: ortools.sat.python.cp_model_helper.LinearExpr, arg1: ortools.sat.python.cp_model_helper.LinearExpr, arg2: ortools.util.python.sorted_interval_list.Domain) -> None
Base class for constraints.
Constraints are built by the CpModel through the add
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())
__init__(self: ortools.sat.python.cp_model_helper.Constraint, arg0: ortools.sat.python.cp_model_helper.CpBaseModel, arg1: int) -> None
(arg0: ortools.sat.python.cp_model_helper.Constraint) -> operations_research::sat::CpModelProto
(arg0: ortools.sat.python.cp_model_helper.Constraint) -> operations_research::sat::ConstraintProto
with_name(self: ortools.sat.python.cp_model_helper.Constraint, arg0: str) -> ortools.sat.python.cp_model_helper.Constraint
Sets the name of the constraint and returns the constraints
only_enforce_if(args, *kwargs) Overloaded function.
only_enforce_if(self: ortools.sat.python.cp_model_helper.Constraint, literal: ortools.sat.python.cp_model_helper.Literal) -> ortools.sat.python.cp_model_helper.Constraint
Adds one or more enforcement literals 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.
Args: *literals: One or more Boolean literals.
Returns: self.
only_enforce_if(self: ortools.sat.python.cp_model_helper.Constraint, literal: bool) -> ortools.sat.python.cp_model_helper.Constraint
Adds one or more enforcement literals 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.
Args: *literals: One or more Boolean literals.
Returns: self.
only_enforce_if(self: ortools.sat.python.cp_model_helper.Constraint, literals: list[ortools.sat.python.cp_model_helper.Literal]) -> None
Adds one or more enforcement literals 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.
Args: *literals: One or more Boolean literals.
Returns: self.
only_enforce_if(self: ortools.sat.python.cp_model_helper.Constraint, *args) -> None
Adds one or more enforcement literals 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.
Args: *literals: One or more Boolean literals.
Returns: self.
Proto(self: ortools.sat.python.cp_model_helper.Constraint) -> operations_research::sat::ConstraintProto
WithName(self: ortools.sat.python.cp_model_helper.Constraint, arg0: str) -> ortools.sat.python.cp_model_helper.Constraint
OnlyEnforceIf(self: ortools.sat.python.cp_model_helper.Constraint, *args) -> None
copy_from(self: ortools.sat.python.cp_model_helper.CpModelProto, arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> None
merge_from(self: ortools.sat.python.cp_model_helper.CpModelProto, arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> None
merge_text_format(self: ortools.sat.python.cp_model_helper.CpModelProto, arg0: str) -> bool
parse_text_format(self: ortools.sat.python.cp_model_helper.CpModelProto, arg0: str) -> bool
(arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> google::protobuf::RepeatedPtrField
(arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> google::protobuf::RepeatedPtrField
clear_objective(self: ortools.sat.python.cp_model_helper.CpModelProto) -> None
has_objective(self: ortools.sat.python.cp_model_helper.CpModelProto) -> bool
(arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> operations_research::sat::FloatObjectiveProto
clear_floating_point_objective(self: ortools.sat.python.cp_model_helper.CpModelProto) -> None
has_floating_point_objective(self: ortools.sat.python.cp_model_helper.CpModelProto) -> bool
(arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> google::protobuf::RepeatedPtrField
(arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> operations_research::sat::PartialVariableAssignment
clear_solution_hint(self: ortools.sat.python.cp_model_helper.CpModelProto) -> None
has_solution_hint(self: ortools.sat.python.cp_model_helper.CpModelProto) -> bool
(arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> google::protobuf::RepeatedField
(arg0: ortools.sat.python.cp_model_helper.CpModelProto) -> operations_research::sat::SymmetryProto
clear_symmetry(self: ortools.sat.python.cp_model_helper.CpModelProto) -> None
has_symmetry(self: ortools.sat.python.cp_model_helper.CpModelProto) -> bool
copy_from(self: ortools.sat.python.cp_model_helper.CpSolverResponse, arg0: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
merge_from(self: ortools.sat.python.cp_model_helper.CpSolverResponse, arg0: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
merge_text_format(self: ortools.sat.python.cp_model_helper.CpSolverResponse, arg0: str) -> bool
parse_text_format(self: ortools.sat.python.cp_model_helper.CpSolverResponse, arg0: str) -> bool
(arg0: ortools.sat.python.cp_model_helper.CpSolverResponse) -> operations_research::sat::CpSolverStatus
clear_status(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
(arg0: ortools.sat.python.cp_model_helper.CpSolverResponse) -> google::protobuf::RepeatedField
clear_objective_value(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_best_objective_bound(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
(arg0: ortools.sat.python.cp_model_helper.CpSolverResponse) -> google::protobuf::RepeatedPtrField
(arg0: ortools.sat.python.cp_model_helper.CpSolverResponse) -> google::protobuf::RepeatedPtrField
(arg0: ortools.sat.python.cp_model_helper.CpSolverResponse) -> google::protobuf::RepeatedField
(arg0: ortools.sat.python.cp_model_helper.CpSolverResponse) -> operations_research::sat::CpObjectiveProto
clear_integer_objective(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
has_integer_objective(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> bool
clear_inner_objective_lower_bound(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_integers(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_booleans(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_fixed_booleans(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_conflicts(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_branches(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_binary_propagations(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_integer_propagations(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_restarts(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_num_lp_iterations(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_wall_time(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_user_time(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_deterministic_time(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_gap_integral(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_solution_info(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
clear_solve_log(self: ortools.sat.python.cp_model_helper.CpSolverResponse) -> None
Members:
UNKNOWN
MODEL_INVALID
FEASIBLE
INFEASIBLE
OPTIMAL
__init__(self: ortools.sat.python.cp_model_helper.CpSolverStatus, value: int) -> None
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.
__init__(self: ortools.util.python.sorted_interval_list.Domain, arg0: int, arg1: int) -> None
By default, Domain will be empty.
all_values() -> ortools.util.python.sorted_interval_list.Domain
Returns the full domain Int64.
greater_or_equal(value: int) -> ortools.util.python.sorted_interval_list.Domain
Returns the domain [value., int_max].
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
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).
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).
lower_or_equal(value: int) -> ortools.util.python.sorted_interval_list.Domain
Returns the domain [int_min, value].
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}.
complement(self: ortools.util.python.sorted_interval_list.Domain) -> ortools.util.python.sorted_interval_list.Domain
Returns the set Int64 ∖ D.
contains(self: ortools.util.python.sorted_interval_list.Domain, value: int) -> bool
Returns true iff value is in Domain.
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
intersection_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 the intersection of D and domain.
is_empty(self: ortools.util.python.sorted_interval_list.Domain) -> bool
Returns true if this is the empty set.
is_included_in(self: ortools.util.python.sorted_interval_list.Domain, domain: ortools.util.python.sorted_interval_list.Domain) -> bool
Returns true iff D is included in the given domain.
size(self: ortools.util.python.sorted_interval_list.Domain) -> int
Returns the number of elements in the domain. It is capped at kint64max
max(self: ortools.util.python.sorted_interval_list.Domain) -> int
Returns the max value of the domain. The domain must not be empty.
min(self: ortools.util.python.sorted_interval_list.Domain) -> int
Returns the min value of the domain. The domain must not be empty.
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} !!
overlaps_with(self: ortools.util.python.sorted_interval_list.Domain, domain: ortools.util.python.sorted_interval_list.Domain) -> bool
- Returns true iff D overlaps with the given domain, that is, the intersection of the two domains is not empty.
union_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 the union of D and domain.
AllValues() -> ortools.util.python.sorted_interval_list.Domain
Returns the full domain Int64.
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
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).
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).
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
A flattened and optimized floating point linear expression.
It can be used to cache complex expressions as parsing them is only done once.
__init__(self: ortools.sat.python.cp_model_helper.FlatFloatExpr, arg0: ortools.sat.python.cp_model_helper.LinearExpr) -> None
(arg0: ortools.sat.python.cp_model_helper.FlatFloatExpr) -> list[operations_research::sat::python::IntVar]
Inherited Members
A flattened and optimized integer linear expression.
It can be used to cache complex expressions as parsing them is only done once.
__init__(self: ortools.sat.python.cp_model_helper.FlatIntExpr, arg0: ortools.sat.python.cp_model_helper.LinearExpr) -> None
(arg0: ortools.sat.python.cp_model_helper.FlatIntExpr) -> list[operations_research::sat::python::IntVar]
Inherited Members
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.
__init__(self: ortools.sat.python.cp_model_helper.IntervalVar, arg0: operations_research::sat::CpModelProto, arg1: int) -> None
(arg0: ortools.sat.python.cp_model_helper.IntervalVar) -> operations_research::sat::CpModelProto
(arg0: ortools.sat.python.cp_model_helper.IntervalVar) -> operations_research::sat::ConstraintProto
start_expr(self: ortools.sat.python.cp_model_helper.IntervalVar) -> object
Returns the start expression of the interval variable.
size_expr(self: ortools.sat.python.cp_model_helper.IntervalVar) -> object
Returns the size expression of the interval variable.
end_expr(self: ortools.sat.python.cp_model_helper.IntervalVar) -> object
Returns the end expression of the interval variable.
presence_literals(self: ortools.sat.python.cp_model_helper.IntervalVar) -> list[ortools.sat.python.cp_model_helper.Literal]
Returns the list of enforcement literals of the interval variable.
Proto(self: ortools.sat.python.cp_model_helper.IntervalVar) -> operations_research::sat::ConstraintProto
A class to hold an integer or Boolean variable
__init__(args, *kwargs) Overloaded function.
__init__(self: ortools.sat.python.cp_model_helper.IntVar, arg0: operations_research::sat::CpModelProto, arg1: int) -> None
__init__(self: ortools.sat.python.cp_model_helper.IntVar, arg0: operations_research::sat::CpModelProto) -> None
(arg0: ortools.sat.python.cp_model_helper.IntVar) -> operations_research::sat::IntegerVariableProto
(arg0: ortools.sat.python.cp_model_helper.IntVar) -> operations_research::sat::CpModelProto
with_name(self: ortools.sat.python.cp_model_helper.IntVar, name: str) -> ortools.sat.python.cp_model_helper.IntVar
Sets the name of the variable and returns the variable.
with_domain(self: ortools.sat.python.cp_model_helper.IntVar, domain: ortools.util.python.sorted_interval_list.Domain) -> ortools.sat.python.cp_model_helper.IntVar
Sets the domain of the variable and returns the variable.
negated(self: ortools.sat.python.cp_model_helper.IntVar) -> ortools.sat.python.cp_model_helper.Literal
Returns the negation of the current variable.
Proto(self: ortools.sat.python.cp_model_helper.IntVar) -> operations_research::sat::IntegerVariableProto
Inherited Members
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)
sum(*args) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns the sum(expressions).
weighted_sum(expressions: Sequence, coefficients: Sequence) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns the sum of (expressions[i] * coefficients[i])
term(args, *kwargs) Overloaded function.
- term(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: int) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns expr * coeff.
- term(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: float) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns expr * coeff.
affine(args, *kwargs) Overloaded function.
- affine(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: int, offset: int) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns expr * coeff + offset.
- affine(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: float, offset: float) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns expr * coeff + offset.
constant(args, *kwargs) Overloaded function.
- constant(value: int) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns a new LinearExpr that is the given constant.
- constant(value: float) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns a new LinearExpr that is the given constant.
WeightedSum(expressions: Sequence, coefficients: Sequence) -> ortools.sat.python.cp_model_helper.LinearExpr
Term(args, *kwargs) Overloaded function.
- Term(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: int) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns expr * coeff.
- Term(expr: ortools.sat.python.cp_model_helper.LinearExpr, coeff: float) -> ortools.sat.python.cp_model_helper.LinearExpr
Returns expr * coeff.
A class to hold a negated variable index.
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.
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.
copy_from(self: ortools.sat.python.cp_model_helper.SatParameters, arg0: ortools.sat.python.cp_model_helper.SatParameters) -> None
merge_from(self: ortools.sat.python.cp_model_helper.SatParameters, arg0: ortools.sat.python.cp_model_helper.SatParameters) -> None
merge_text_format(self: ortools.sat.python.cp_model_helper.SatParameters, arg0: str) -> bool
parse_text_format(self: ortools.sat.python.cp_model_helper.SatParameters, arg0: str) -> bool
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_VariableOrder
clear_preferred_variable_order(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_Polarity
clear_initial_polarity(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_phase_saving(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_polarity_rephase_increment(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_polarity_exploit_ls_hints(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_random_polarity_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_random_branches_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_erwa_heuristic(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_initial_variables_activity(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_also_bump_variables_in_conflict_reasons(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_ConflictMinimizationAlgorithm
clear_minimization_algorithm(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_BinaryMinizationAlgorithm
clear_binary_minimization_algorithm(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_subsumption_during_conflict_analysis(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_extra_subsumption_during_conflict_analysis(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_decision_subsumption_during_conflict_analysis(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_eagerly_subsume_last_n_conflicts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_subsume_during_vivification(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_chronological_backtracking(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_backjump_levels(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_chronological_backtrack_min_conflicts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_clause_cleanup_period(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_clause_cleanup_period_increment(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_clause_cleanup_target(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_clause_cleanup_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_clause_cleanup_lbd_bound(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_clause_cleanup_lbd_tier1(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_clause_cleanup_lbd_tier2(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_ClauseOrdering
clear_clause_cleanup_ordering(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_pb_cleanup_increment(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_pb_cleanup_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_variable_activity_decay(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_variable_activity_value(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_glucose_max_decay(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_glucose_decay_increment(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_glucose_decay_increment_period(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_clause_activity_decay(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_clause_activity_value(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> google::protobuf::RepeatedField
clear_default_restart_algorithms(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_restart_period(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_restart_running_window_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_restart_dl_average_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_restart_lbd_average_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_blocking_restart(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_blocking_restart_window_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_blocking_restart_multiplier(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_num_conflicts_before_strategy_changes(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_strategy_change_increase_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_time_in_seconds(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_deterministic_time(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_num_deterministic_batches(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_number_of_conflicts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_memory_in_mb(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_absolute_gap_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_relative_gap_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_random_seed(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_permute_variable_randomly(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_permute_presolve_constraint_order(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_absl_random(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_log_search_progress(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_log_subsolver_statistics(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_log_prefix(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_log_to_stdout(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_log_to_response(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_pb_resolution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_minimize_reduction_during_pb_resolution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_count_assumption_levels_in_lbd(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_presolve_bve_threshold(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_filter_sat_postsolve_clauses(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_presolve_bve_clause_weight(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_probing_deterministic_time_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> float
clear_presolve_probing_deterministic_time_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_presolve_blocked_clause(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_presolve_use_bva(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_presolve_bva_threshold(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_presolve_iterations(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_cp_model_presolve(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_cp_model_probing_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_cp_model_use_sat_presolve(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_load_at_most_ones_in_sat_presolve(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_remove_fixed_variables_early(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_detect_table_with_cost(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_table_compression_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_expand_alldiff_constraints(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_alldiff_domain_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_expand_reservoir_constraints(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_max_domain_size_for_linear2_expansion(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_expand_reservoir_using_circuit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_encode_cumulative_as_reservoir(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_lin_max_size_for_expansion(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_disable_constraint_expansion(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_encode_complex_linear_constraint_with_integer(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_merge_no_overlap_work_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_merge_at_most_one_work_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_presolve_substitution_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_presolve_extract_integer_enforcement(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_presolve_inclusion_work_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_ignore_names(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_infer_all_diffs(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_find_big_linear_overlap(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_find_clauses_that_are_exactly_one(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_sat_inprocessing(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_inprocessing_dtime_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_inprocessing_probing_dtime(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_inprocessing_minimization_dtime(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_inprocessing_minimization_use_conflict_analysis(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_inprocessing_minimization_use_all_orderings(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_inprocessing_use_congruence_closure(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_inprocessing_use_sat_sweeping(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_num_workers(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_num_search_workers(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_num_full_subsolvers(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> google::protobuf::RepeatedPtrField
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> google::protobuf::RepeatedPtrField
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> google::protobuf::RepeatedPtrField
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> google::protobuf::RepeatedPtrField
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> google::protobuf::RepeatedPtrField
clear_interleave_search(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_interleave_batch_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_check_lrat_proof(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_check_merged_lrat_proof(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_output_lrat_proof(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_check_drat_proof(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_output_drat_proof(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_drat_time_in_seconds(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_debug_postsolve_with_full_solver(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_debug_max_num_presolve_operations(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_debug_crash_on_bad_hint(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_debug_crash_if_presolve_breaks_hint(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_debug_crash_if_lrat_check_fails(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_optimization_hints(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_core_minimization_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_find_multiple_cores(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_cover_optimization(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_MaxSatAssumptionOrder
clear_max_sat_assumption_order(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_sat_reverse_assumption_order(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_MaxSatStratificationAlgorithm
clear_max_sat_stratification(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_propagation_loop_detection_factor(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_precedences_in_disjunctive_constraint(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_transitive_precedences_work_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_max_size_to_create_precedence_literals_in_disjunctive(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_strong_propagation_in_disjunctive(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_dynamic_precedence_in_disjunctive(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_dynamic_precedence_in_cumulative(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_overload_checker_in_cumulative(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_conservative_scale_overload_checker(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_timetable_edge_finding_in_cumulative(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_max_num_intervals_for_timetable_edge_finding(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_hard_precedences_in_cumulative(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_exploit_all_precedences(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_disjunctive_constraint_in_cumulative(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_no_overlap_2d_boolean_relations_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_timetabling_in_no_overlap_2d(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_energetic_reasoning_in_no_overlap_2d(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_area_energetic_reasoning_in_no_overlap_2d(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_try_edge_reasoning_in_no_overlap_2d(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_max_pairs_pairwise_reasoning_in_no_overlap_2d(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_maximum_regions_to_split_in_disconnected_no_overlap_2d(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_linear3_for_no_overlap_2d_precedences(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_dual_scheduling_heuristics(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_all_different_for_circuit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_routing_cut_subset_size_for_binary_relation_bound(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_routing_cut_subset_size_for_tight_binary_relation_bound(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_routing_cut_subset_size_for_exact_binary_relation_bound(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_routing_cut_subset_size_for_shortest_paths_bound(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_routing_cut_dp_effort(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_routing_cut_max_infeasible_path_length(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_SearchBranching
clear_search_branching(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_hint_conflict_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_repair_hint(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_fix_variables_to_their_hinted_value(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_probing_search(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_extended_probing(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_probing_num_combinations_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> float
clear_shaving_deterministic_time_in_probing_search(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_shaving_search_deterministic_time(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_shaving_search_threshold(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_objective_lb_search(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_objective_shaving_search(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_variables_shaving_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_pseudo_cost_reliability_threshold(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_optimize_with_core(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_optimize_with_lb_tree_search(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_save_lp_basis_in_lb_tree_search(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_binary_search_num_conflicts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_optimize_with_max_hs(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_feasibility_jump(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_ls_only(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_feasibility_jump_decay(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_feasibility_jump_linearization_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_feasibility_jump_restart_factor(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_feasibility_jump_batch_dtime(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> float
clear_feasibility_jump_var_randomization_probability(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> float
clear_feasibility_jump_var_perburbation_range_ratio(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_feasibility_jump_enable_restarts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_feasibility_jump_max_expanded_constraint_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_num_violation_ls(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_violation_ls_perturbation_period(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> float
clear_violation_ls_compound_move_probability(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_enumerate_all_solutions(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_keep_all_feasible_solutions_in_presolve(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_fill_tightened_domains_in_response(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_fill_additional_solutions_in_response(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_instantiate_all_variables(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_auto_detect_greater_than_at_least_one_of(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_stop_after_first_solution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_stop_after_presolve(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_stop_after_root_propagation(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_lns_initial_difficulty(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_lns_initial_deterministic_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_lns(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_lns_only(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_solution_pool_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_solution_pool_diversity_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_alternative_pool_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_rins_lns(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_feasibility_pump(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_lb_relax_lns(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_lb_relax_num_workers_threshold(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> operations_research::sat::SatParameters_FPRoundingMethod
clear_fp_rounding(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_diversify_lns_params(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_randomize_search(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_search_random_variable_pool_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_push_all_tasks_toward_start(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_optional_variables(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_exact_lp_reason(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_combined_no_overlap(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_at_most_one_max_expansion_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_catch_sigint_signal(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_implied_bounds(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_polish_lp_solution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_lp_primal_tolerance(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_lp_dual_tolerance(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_convert_intervals(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_symmetry_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_use_symmetry_in_lp(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_keep_symmetry_in_presolve(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> float
clear_symmetry_detection_deterministic_time_limit(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_new_linear_propagation(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_linear_split_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_linearization_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_boolean_encoding_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> int
clear_max_domain_size_when_encoding_eq_neq_constraints(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_num_cuts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_cut_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_only_add_cuts_at_level_zero(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_add_objective_cut(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_add_cg_cuts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_add_mir_cuts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_add_zero_half_cuts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_add_clique_cuts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_add_rlt_cuts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_all_diff_cut_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_add_lin_max_cuts(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_integer_rounding_scaling(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_add_lp_constraints_lazily(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_root_lp_iterations(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> float
clear_min_orthogonality_for_lp_constraints(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_cut_rounds_at_level_zero(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_max_consecutive_inactive_count(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_cut_max_active_count_value(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_cut_active_count_decay(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_cut_cleanup_target(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_new_constraints_batch_size(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_exploit_integer_lp_solution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_exploit_all_lp_solution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_exploit_best_solution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_exploit_relaxation_solution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_exploit_objective(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_detect_linearized_product(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_use_new_integer_conflict_resolution(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_create_1uip_boolean_during_icr(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_max_bound(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_var_scaling(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_scale_large_domain(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_automatically_scale_variables(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_only_solve_ip(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_wanted_precision(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_max_activity_exponent(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_check_precision(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_compute_true_objective_bound(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_max_valid_magnitude(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
(arg0: ortools.sat.python.cp_model_helper.SatParameters) -> bool
clear_mip_treat_high_magnitude_bounds_as_infinity(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_drop_tolerance(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
clear_mip_presolve_level(self: ortools.sat.python.cp_model_helper.SatParameters) -> None
Members:
AUTOMATIC_SEARCH
FIXED_SEARCH
PORTFOLIO_SEARCH
LP_SEARCH
PSEUDO_COST_SEARCH
PORTFOLIO_WITH_QUICK_RESTART_SEARCH
HINT_SEARCH
PARTIAL_FIXED_SEARCH
RANDOMIZED_SEARCH
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.SearchBranching, value: int) -> None
Members:
STRATIFICATION_NONE
STRATIFICATION_DESCENT
STRATIFICATION_ASCENT
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.MaxSatStratificationAlgorithm, value: int) -> None
Members:
POLARITY_TRUE
POLARITY_FALSE
POLARITY_RANDOM
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.Polarity, value: int) -> None
Members:
IN_ORDER
IN_REVERSE_ORDER
IN_RANDOM_ORDER
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.VariableOrder, value: int) -> None
Members:
NONE
SIMPLE
RECURSIVE
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.ConflictMinimizationAlgorithm, value: int) -> None
Members:
CLAUSE_ACTIVITY
CLAUSE_LBD
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.ClauseOrdering, value: int) -> None
Members:
DEFAULT_ASSUMPTION_ORDER
ORDER_ASSUMPTION_BY_DEPTH
ORDER_ASSUMPTION_BY_WEIGHT
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.MaxSatAssumptionOrder, value: int) -> None
Members:
NO_BINARY_MINIMIZATION
BINARY_MINIMIZATION_FROM_UIP
BINARY_MINIMIZATION_FROM_UIP_AND_DECISIONS
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.BinaryMinizationAlgorithm, value: int) -> None
Members:
NEAREST_INTEGER
LOCK_BASED
ACTIVE_LOCK_BASED
PROPAGATION_ASSISTED
__init__(self: ortools.sat.python.cp_model_helper.SatParameters.FPRoundingMethod, value: int) -> None
192def deprecated(message: str) -> Callable[[Callable], Callable]: 193 """Decorator that warns about a deprecated function.""" 194 195 def deprecated_decorator(func) -> Callable: 196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs) 205 206 return deprecated_func 207 208 return deprecated_decorator
Decorator that warns about a deprecated function.
211def deprecated_method(func, old_name: str) -> Callable: 212 """Wrapper that warns about a deprecated method.""" 213 214 def deprecated_func(*args, **kwargs) -> Any: 215 if enable_warnings: 216 warnings.warn( 217 f"{old_name} is a deprecated function. Use {func.__name__} instead.", 218 category=DeprecationWarning, 219 stacklevel=2, 220 ) 221 warnings.simplefilter("default", DeprecationWarning) 222 return func(*args, **kwargs) 223 224 return deprecated_func
Wrapper that warns about a deprecated method.
230def snake_case_to_camel_case(name: str) -> str: 231 """Converts a snake_case name to CamelCase.""" 232 words = name.split("_") 233 return ( 234 "".join(word.capitalize() for word in words) 235 .replace("2d", "2D") 236 .replace("Xor", "XOr") 237 )
Converts a snake_case name to CamelCase.
240def object_is_a_true_literal(literal: LiteralT) -> bool: 241 """Checks if literal is either True, or a Boolean literals fixed to True.""" 242 if isinstance(literal, IntVar): 243 proto = literal.proto 244 return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1 245 if isinstance(literal, cmh.NotBooleanVariable): 246 proto = literal.negated().proto 247 return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0 248 if isinstance(literal, (bool, np.bool_)): 249 return bool(literal) 250 if isinstance(literal, IntegralTypes): 251 literal_as_int = int(literal) 252 return literal_as_int == 1 or literal_as_int == ~False 253 return False
Checks if literal is either True, or a Boolean literals fixed to True.
256def object_is_a_false_literal(literal: LiteralT) -> bool: 257 """Checks if literal is either False, or a Boolean literals fixed to False.""" 258 if isinstance(literal, IntVar): 259 proto = literal.proto 260 return len(proto.domain) == 2 and proto.domain[0] == 0 and proto.domain[1] == 0 261 if isinstance(literal, cmh.NotBooleanVariable): 262 proto = literal.negated().proto 263 return len(proto.domain) == 2 and proto.domain[0] == 1 and proto.domain[1] == 1 264 if isinstance(literal, (bool, np.bool_)): 265 return not bool(literal) 266 if isinstance(literal, IntegralTypes): 267 literal_as_int = int(literal) 268 return literal_as_int == 0 or literal_as_int == ~True 269 return False
Checks if literal is either False, or a Boolean literals fixed to False.
307class CpModel(cmh.CpBaseModel): 308 """Methods for building a CP model. 309 310 Methods beginning with: 311 312 * ```new_``` create integer, boolean, or interval variables. 313 * ```add_``` create new constraints and add them to the model. 314 """ 315 316 def __init__(self, model_proto: Optional[cmh.CpModelProto] = None) -> None: 317 cmh.CpBaseModel.__init__(self, model_proto) 318 self._add_pre_pep8_methods() 319 320 # Naming. 321 @property 322 def name(self) -> str: 323 """Returns the name of the model.""" 324 if not self.model_proto or not self.model_proto.name: 325 return "" 326 return self.model_proto.name 327 328 @name.setter 329 def name(self, name: str): 330 """Sets the name of the model.""" 331 self.model_proto.name = name 332 333 # Integer variable. 334 def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: 335 """Create an integer variable with domain [lb, ub]. 336 337 The CP-SAT solver is limited to integer variables. If you have fractional 338 values, scale them up so that they become integers; if you have strings, 339 encode them as integers. 340 341 Args: 342 lb: Lower bound for the variable. 343 ub: Upper bound for the variable. 344 name: The name of the variable. 345 346 Returns: 347 a variable whose domain is [lb, ub]. 348 """ 349 return ( 350 IntVar(self.model_proto) 351 .with_name(name) 352 .with_domain(sorted_interval_list.Domain(lb, ub)) 353 ) 354 355 def new_int_var_from_domain( 356 self, domain: sorted_interval_list.Domain, name: str 357 ) -> IntVar: 358 """Create an integer variable from a domain. 359 360 A domain is a set of integers specified by a collection of intervals. 361 For example, `model.new_int_var_from_domain(cp_model. 362 Domain.from_intervals([[1, 2], [4, 6]]), 'x')` 363 364 Args: 365 domain: An instance of the Domain class. 366 name: The name of the variable. 367 368 Returns: 369 a variable whose domain is the given domain. 370 """ 371 return IntVar(self.model_proto).with_name(name).with_domain(domain) 372 373 def new_bool_var(self, name: str) -> IntVar: 374 """Creates a 0-1 variable with the given name.""" 375 return ( 376 IntVar(self.model_proto) 377 .with_name(name) 378 .with_domain(sorted_interval_list.Domain(0, 1)) 379 ) 380 381 def new_constant(self, value: IntegralT) -> IntVar: 382 """Declares a constant integer.""" 383 return IntVar(self.model_proto, self.get_or_make_index_from_constant(value)) 384 385 def new_int_var_series( 386 self, 387 name: str, 388 index: pd.Index, 389 lower_bounds: Union[IntegralT, pd.Series], 390 upper_bounds: Union[IntegralT, pd.Series], 391 ) -> pd.Series: 392 """Creates a series of (scalar-valued) variables with the given name. 393 394 Args: 395 name (str): Required. The name of the variable set. 396 index (pd.Index): Required. The index to use for the variable set. 397 lower_bounds (Union[int, pd.Series]): A lower bound for variables in the 398 set. If a `pd.Series` is passed in, it will be based on the 399 corresponding values of the pd.Series. 400 upper_bounds (Union[int, pd.Series]): An upper bound for variables in the 401 set. If a `pd.Series` is passed in, it will be based on the 402 corresponding values of the pd.Series. 403 404 Returns: 405 pd.Series: The variable set indexed by its corresponding dimensions. 406 407 Raises: 408 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 409 ValueError: if the `name` is not a valid identifier or already exists. 410 ValueError: if the `lowerbound` is greater than the `upperbound`. 411 ValueError: if the index of `lower_bound`, or `upper_bound` does not match 412 the input index. 413 """ 414 if not isinstance(index, pd.Index): 415 raise TypeError("Non-index object is used as index") 416 if not name.isidentifier(): 417 raise ValueError(f"name={name!r} is not a valid identifier") 418 if ( 419 isinstance(lower_bounds, IntegralTypes) 420 and isinstance(upper_bounds, IntegralTypes) 421 and lower_bounds > upper_bounds 422 ): 423 raise ValueError( 424 f"lower_bound={lower_bounds} is greater than" 425 f" upper_bound={upper_bounds} for variable set={name}" 426 ) 427 428 lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index) 429 upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index) 430 return pd.Series( 431 index=index, 432 data=[ 433 # pylint: disable=g-complex-comprehension 434 IntVar(self.model_proto) 435 .with_name(f"{name}[{i}]") 436 .with_domain( 437 sorted_interval_list.Domain(lower_bounds[i], upper_bounds[i]) 438 ) 439 for i in index 440 ], 441 ) 442 443 def new_bool_var_series( 444 self, 445 name: str, 446 index: pd.Index, 447 ) -> pd.Series: 448 """Creates a series of (scalar-valued) variables with the given name. 449 450 Args: 451 name (str): Required. The name of the variable set. 452 index (pd.Index): Required. The index to use for the variable set. 453 454 Returns: 455 pd.Series: The variable set indexed by its corresponding dimensions. 456 457 Raises: 458 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 459 ValueError: if the `name` is not a valid identifier or already exists. 460 """ 461 if not isinstance(index, pd.Index): 462 raise TypeError("Non-index object is used as index") 463 if not name.isidentifier(): 464 raise ValueError(f"name={name!r} is not a valid identifier") 465 return pd.Series( 466 index=index, 467 data=[ 468 # pylint: disable=g-complex-comprehension 469 IntVar(self.model_proto) 470 .with_name(f"{name}[{i}]") 471 .with_domain(sorted_interval_list.Domain(0, 1)) 472 for i in index 473 ], 474 ) 475 476 # Linear constraints. 477 478 def add_linear_constraint( 479 self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT 480 ) -> Constraint: 481 """Adds the constraint: `lb <= linear_expr <= ub`.""" 482 return self.add_linear_expression_in_domain( 483 linear_expr, sorted_interval_list.Domain(lb, ub) 484 ) 485 486 def add_linear_expression_in_domain( 487 self, 488 linear_expr: LinearExprT, 489 domain: sorted_interval_list.Domain, 490 ) -> Constraint: 491 """Adds the constraint: `linear_expr` in `domain`.""" 492 if isinstance(linear_expr, LinearExpr): 493 ble = BoundedLinearExpression(linear_expr, domain) 494 if not ble.ok: 495 raise TypeError( 496 "Cannot add a linear expression containing floating point" 497 f" coefficients or constants: {type(linear_expr).__name__!r}" 498 ) 499 return self._add_bounded_linear_expression(ble) 500 if isinstance(linear_expr, IntegralTypes): 501 if not domain.contains(int(linear_expr)): 502 return self.add_bool_or([]) # Evaluate to false. 503 else: 504 return self.add_bool_and([]) # Evaluate to true. 505 raise TypeError( 506 "not supported:" 507 f" CpModel.add_linear_expression_in_domain({type(linear_expr).__name__!r})" 508 ) 509 510 def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint: 511 """Adds a `BoundedLinearExpression` to the model. 512 513 Args: 514 ct: A [`BoundedLinearExpression`](#boundedlinearexpression). 515 516 Returns: 517 An instance of the `Constraint` class. 518 519 Raises: 520 TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean. 521 """ 522 if isinstance(ct, BoundedLinearExpression): 523 return self._add_bounded_linear_expression(ct) 524 if ct and self.is_boolean_value(ct): 525 return self.add_bool_or([True]) 526 if not ct and self.is_boolean_value(ct): 527 return self.add_bool_or([]) # Evaluate to false. 528 raise TypeError(f"not supported: CpModel.add({type(ct).__name__!r})") 529 530 # General Integer Constraints. 531 532 @overload 533 def add_all_different(self, expressions: Iterable[LinearExprT]) -> Constraint: ... 534 535 @overload 536 def add_all_different(self, *expressions: LinearExprT) -> Constraint: ... 537 538 def add_all_different(self, *expressions): 539 """Adds AllDifferent(expressions). 540 541 This constraint forces all expressions to have different values. 542 543 Args: 544 *expressions: simple expressions of the form a * var + constant. 545 546 Returns: 547 An instance of the `Constraint` class. 548 """ 549 return self._add_all_different(*expressions) 550 551 def add_element( 552 self, 553 index: LinearExprT, 554 expressions: Sequence[LinearExprT], 555 target: LinearExprT, 556 ) -> Constraint: 557 """Adds the element constraint: `expressions[index] == target`. 558 559 Args: 560 index: The index of the selected expression in the array. It must be an 561 affine expression (a * var + b). 562 expressions: A list of affine expressions. 563 target: The expression constrained to be equal to the selected expression. 564 It must be an affine expression (a * var + b). 565 566 Returns: 567 An instance of the `Constraint` class. 568 """ 569 570 if not expressions: 571 raise ValueError("add_element expects a non-empty expressions array") 572 573 if isinstance(index, IntegralTypes): 574 expression: LinearExprT = list(expressions)[int(index)] 575 return self.add(expression == target) 576 577 return self._add_element(index, expressions, target) 578 579 def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint: 580 """Adds Circuit(arcs). 581 582 Adds a circuit constraint from a sparse list of arcs that encode the graph. 583 584 A circuit is a unique Hamiltonian cycle in a subgraph of the total 585 graph. In case a node 'i' is not in the cycle, then there must be a 586 loop arc 'i -> i' associated with a true literal. Otherwise 587 this constraint will fail. 588 589 Args: 590 arcs: a list of arcs. An arc is a tuple (source_node, destination_node, 591 literal). The arc is selected in the circuit if the literal is true. 592 Both source_node and destination_node must be integers between 0 and the 593 number of nodes - 1. 594 595 Returns: 596 An instance of the `Constraint` class. 597 598 Raises: 599 ValueError: If the list of arcs is empty. 600 """ 601 if not arcs: 602 raise ValueError("add_circuit expects a non-empty array of arcs") 603 return self._add_circuit(arcs) 604 605 def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint: 606 """Adds a multiple circuit constraint, aka the 'VRP' constraint. 607 608 The direct graph where arc #i (from tails[i] to head[i]) is present iff 609 literals[i] is true must satisfy this set of properties: 610 - #incoming arcs == 1 except for node 0. 611 - #outgoing arcs == 1 except for node 0. 612 - for node zero, #incoming arcs == #outgoing arcs. 613 - There are no duplicate arcs. 614 - Self-arcs are allowed except for node 0. 615 - There is no cycle in this graph, except through node 0. 616 617 Args: 618 arcs: a list of arcs. An arc is a tuple (source_node, destination_node, 619 literal). The arc is selected in the circuit if the literal is true. 620 Both source_node and destination_node must be integers between 0 and the 621 number of nodes - 1. 622 623 Returns: 624 An instance of the `Constraint` class. 625 626 Raises: 627 ValueError: If the list of arcs is empty. 628 """ 629 if not arcs: 630 raise ValueError("add_multiple_circuit expects a non-empty array of arcs") 631 return self._add_routes(arcs) 632 633 def add_allowed_assignments( 634 self, 635 expressions: Sequence[LinearExprT], 636 tuples_list: Iterable[Sequence[IntegralT]], 637 ) -> Constraint: 638 """Adds AllowedAssignments(expressions, tuples_list). 639 640 An AllowedAssignments constraint is a constraint on an array of affine 641 expressions, which requires that when all expressions are assigned values, 642 the 643 resulting array equals one of the tuples in `tuple_list`. 644 645 Args: 646 expressions: A list of affine expressions (a * var + b). 647 tuples_list: A list of admissible tuples. Each tuple must have the same 648 length as the expressions, and the ith value of a tuple corresponds to 649 the ith expression. 650 651 Returns: 652 An instance of the `Constraint` class. 653 654 Raises: 655 TypeError: If a tuple does not have the same size as the list of 656 expressions. 657 ValueError: If the array of expressions is empty. 658 """ 659 660 if not expressions: 661 raise ValueError( 662 "add_allowed_assignments expects a non-empty expressions array" 663 ) 664 665 return self._add_table(expressions, tuples_list, False) 666 667 def add_forbidden_assignments( 668 self, 669 expressions: Sequence[LinearExprT], 670 tuples_list: Iterable[Sequence[IntegralT]], 671 ) -> Constraint: 672 """Adds add_forbidden_assignments(expressions, [tuples_list]). 673 674 A ForbiddenAssignments constraint is a constraint on an array of affine 675 expressions where the list of impossible combinations is provided in the 676 tuples list. 677 678 Args: 679 expressions: A list of affine expressions (a * var + b). 680 tuples_list: A list of forbidden tuples. Each tuple must have the same 681 length as the expressions, and the *i*th value of a tuple corresponds to 682 the *i*th expression. 683 684 Returns: 685 An instance of the `Constraint` class. 686 687 Raises: 688 TypeError: If a tuple does not have the same size as the list of 689 expressions. 690 ValueError: If the array of expressions is empty. 691 """ 692 693 if not expressions: 694 raise ValueError( 695 "add_forbidden_assignments expects a non-empty expressions array" 696 ) 697 698 return self._add_table(expressions, tuples_list, True) 699 700 def add_automaton( 701 self, 702 transition_expressions: Sequence[LinearExprT], 703 starting_state: IntegralT, 704 final_states: Sequence[IntegralT], 705 transition_triples: Sequence[tuple[IntegralT, IntegralT, IntegralT]], 706 ) -> Constraint: 707 """Adds an automaton constraint. 708 709 An automaton constraint takes a list of affine expressions (a * var + b) (of 710 size *n*), an initial state, a set of final states, and a set of 711 transitions. A transition is a triplet (*tail*, *transition*, *head*), where 712 *tail* and *head* are states, and *transition* is the label of an arc from 713 *head* to *tail*, corresponding to the value of one expression in the list 714 of 715 expressions. 716 717 This automaton will be unrolled into a flow with *n* + 1 phases. Each phase 718 contains the possible states of the automaton. The first state contains the 719 initial state. The last phase contains the final states. 720 721 Between two consecutive phases *i* and *i* + 1, the automaton creates a set 722 of arcs. For each transition (*tail*, *transition*, *head*), it will add 723 an arc from the state *tail* of phase *i* and the state *head* of phase 724 *i* + 1. This arc is labeled by the value *transition* of the expression 725 `expressions[i]`. That is, this arc can only be selected if `expressions[i]` 726 is assigned the value *transition*. 727 728 A feasible solution of this constraint is an assignment of expressions such 729 that, starting from the initial state in phase 0, there is a path labeled by 730 the values of the expressions that ends in one of the final states in the 731 final phase. 732 733 Args: 734 transition_expressions: A non-empty list of affine expressions (a * var + 735 b) whose values correspond to the labels of the arcs traversed by the 736 automaton. 737 starting_state: The initial state of the automaton. 738 final_states: A non-empty list of admissible final states. 739 transition_triples: A list of transitions for the automaton, in the 740 following format (current_state, variable_value, next_state). 741 742 Returns: 743 An instance of the `Constraint` class. 744 745 Raises: 746 ValueError: if `transition_expressions`, `final_states`, or 747 `transition_triples` are empty. 748 """ 749 750 if not transition_expressions: 751 raise ValueError( 752 "add_automaton expects a non-empty transition_expressions array" 753 ) 754 if not final_states: 755 raise ValueError("add_automaton expects some final states") 756 757 if not transition_triples: 758 raise ValueError("add_automaton expects some transition triples") 759 760 return self._add_automaton( 761 transition_expressions, 762 starting_state, 763 final_states, 764 transition_triples, 765 ) 766 767 def add_inverse( 768 self, 769 variables: Sequence[VariableT], 770 inverse_variables: Sequence[VariableT], 771 ) -> Constraint: 772 """Adds Inverse(variables, inverse_variables). 773 774 An inverse constraint enforces that if `variables[i]` is assigned a value 775 `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa. 776 777 Args: 778 variables: An array of integer variables. 779 inverse_variables: An array of integer variables. 780 781 Returns: 782 An instance of the `Constraint` class. 783 784 Raises: 785 TypeError: if variables and inverse_variables have different lengths, or 786 if they are empty. 787 """ 788 789 if not variables or not inverse_variables: 790 raise TypeError("The Inverse constraint does not accept empty arrays") 791 if len(variables) != len(inverse_variables): 792 raise TypeError( 793 "In the inverse constraint, the two array variables and" 794 " inverse_variables must have the same length." 795 ) 796 return self._add_inverse(variables, inverse_variables) 797 798 def add_reservoir_constraint( 799 self, 800 times: Sequence[LinearExprT], 801 level_changes: Sequence[LinearExprT], 802 min_level: int, 803 max_level: int, 804 ) -> Constraint: 805 """Adds Reservoir(times, level_changes, min_level, max_level). 806 807 Maintains a reservoir level within bounds. The water level starts at 0, and 808 at any time, it must be between min_level and max_level. 809 810 If the affine expression `times[i]` is assigned a value t, then the current 811 level changes by `level_changes[i]`, which is constant, at time t. 812 813 Note that min level must be <= 0, and the max level must be >= 0. Please 814 use fixed level_changes to simulate initial state. 815 816 Therefore, at any time: 817 sum(level_changes[i] if times[i] <= t) in [min_level, max_level] 818 819 Args: 820 times: A list of 1-var affine expressions (a * x + b) which specify the 821 time of the filling or emptying the reservoir. 822 level_changes: A list of integer values that specifies the amount of the 823 emptying or filling. Currently, variable demands are not supported. 824 min_level: At any time, the level of the reservoir must be greater or 825 equal than the min level. 826 max_level: At any time, the level of the reservoir must be less or equal 827 than the max level. 828 829 Returns: 830 An instance of the `Constraint` class. 831 832 Raises: 833 ValueError: if max_level < min_level. 834 835 ValueError: if max_level < 0. 836 837 ValueError: if min_level > 0 838 """ 839 840 return self._add_reservoir( 841 times, 842 level_changes, 843 [], 844 min_level, 845 max_level, 846 ) 847 848 def add_reservoir_constraint_with_active( 849 self, 850 times: Sequence[LinearExprT], 851 level_changes: Sequence[LinearExprT], 852 actives: Sequence[LiteralT], 853 min_level: int, 854 max_level: int, 855 ) -> Constraint: 856 """Adds Reservoir(times, level_changes, actives, min_level, max_level). 857 858 Maintains a reservoir level within bounds. The water level starts at 0, and 859 at any time, it must be between min_level and max_level. 860 861 If the variable `times[i]` is assigned a value t, and `actives[i]` is 862 `True`, then the current level changes by `level_changes[i]`, which is 863 constant, 864 at time t. 865 866 Note that min level must be <= 0, and the max level must be >= 0. Please 867 use fixed level_changes to simulate initial state. 868 869 Therefore, at any time: 870 sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level, 871 max_level] 872 873 874 The array of boolean variables 'actives', if defined, indicates which 875 actions are actually performed. 876 877 Args: 878 times: A list of 1-var affine expressions (a * x + b) which specify the 879 time of the filling or emptying the reservoir. 880 level_changes: A list of integer values that specifies the amount of the 881 emptying or filling. Currently, variable demands are not supported. 882 actives: a list of boolean variables. They indicates if the 883 emptying/refilling events actually take place. 884 min_level: At any time, the level of the reservoir must be greater or 885 equal than the min level. 886 max_level: At any time, the level of the reservoir must be less or equal 887 than the max level. 888 889 Returns: 890 An instance of the `Constraint` class. 891 892 Raises: 893 ValueError: if max_level < min_level. 894 895 ValueError: if max_level < 0. 896 897 ValueError: if min_level > 0 898 """ 899 900 if max_level < min_level: 901 raise ValueError("Reservoir constraint must have a max_level >= min_level") 902 903 if max_level < 0: 904 raise ValueError("Reservoir constraint must have a max_level >= 0") 905 906 if min_level > 0: 907 raise ValueError("Reservoir constraint must have a min_level <= 0") 908 909 if not times: 910 raise ValueError("Reservoir constraint must have a non-empty times array") 911 912 return self._add_reservoir( 913 times, 914 level_changes, 915 actives, 916 min_level, 917 max_level, 918 ) 919 920 def add_map_domain( 921 self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0 922 ): 923 """Adds `var == i + offset <=> bool_var_array[i] == true for all i`.""" 924 for i, bool_var in enumerate(bool_var_array): 925 self.add(var == i + offset).only_enforce_if(bool_var) 926 self.add(var != i + offset).only_enforce_if(~bool_var) 927 928 def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint: 929 """Adds `a => b` (`a` implies `b`).""" 930 return self.add_bool_and(b).only_enforce_if(a) 931 932 @overload 933 def add_bool_or(self, literals: Iterable[LiteralT]) -> Constraint: ... 934 935 @overload 936 def add_bool_or(self, *literals: LiteralT) -> Constraint: ... 937 938 def add_bool_or(self, *literals): 939 """Adds `Or(literals) == true`: sum(literals) >= 1.""" 940 return self._add_bool_argument_constraint( 941 cmh.BoolArgumentConstraint.bool_or, *literals 942 ) 943 944 @overload 945 def add_at_least_one(self, literals: Iterable[LiteralT]) -> Constraint: ... 946 947 @overload 948 def add_at_least_one(self, *literals: LiteralT) -> Constraint: ... 949 950 def add_at_least_one(self, *literals): 951 """Same as `add_bool_or`: `sum(literals) >= 1`.""" 952 return self._add_bool_argument_constraint( 953 cmh.BoolArgumentConstraint.bool_or, *literals 954 ) 955 956 @overload 957 def add_at_most_one(self, literals: Iterable[LiteralT]) -> Constraint: ... 958 959 @overload 960 def add_at_most_one(self, *literals: LiteralT) -> Constraint: ... 961 962 def add_at_most_one(self, *literals) -> Constraint: 963 """Adds `AtMostOne(literals)`: `sum(literals) <= 1`.""" 964 return self._add_bool_argument_constraint( 965 cmh.BoolArgumentConstraint.at_most_one, *literals 966 ) 967 968 @overload 969 def add_exactly_one(self, literals: Iterable[LiteralT]) -> Constraint: ... 970 971 @overload 972 def add_exactly_one(self, *literals: LiteralT) -> Constraint: ... 973 974 def add_exactly_one(self, *literals): 975 """Adds `ExactlyOne(literals)`: `sum(literals) == 1`.""" 976 return self._add_bool_argument_constraint( 977 cmh.BoolArgumentConstraint.exactly_one, *literals 978 ) 979 980 @overload 981 def add_bool_and(self, literals: Iterable[LiteralT]) -> Constraint: ... 982 983 @overload 984 def add_bool_and(self, *literals: LiteralT) -> Constraint: ... 985 986 def add_bool_and(self, *literals): 987 """Adds `And(literals) == true`.""" 988 return self._add_bool_argument_constraint( 989 cmh.BoolArgumentConstraint.bool_and, *literals 990 ) 991 992 @overload 993 def add_bool_xor(self, literals: Iterable[LiteralT]) -> Constraint: ... 994 995 @overload 996 def add_bool_xor(self, *literals: LiteralT) -> Constraint: ... 997 998 def add_bool_xor(self, *literals): 999 """Adds `XOr(literals) == true`. 1000 1001 In contrast to add_bool_or and add_bool_and, it does not support 1002 .only_enforce_if(). 1003 1004 Args: 1005 *literals: the list of literals in the constraint. 1006 1007 Returns: 1008 An `Constraint` object. 1009 """ 1010 return self._add_bool_argument_constraint( 1011 cmh.BoolArgumentConstraint.bool_xor, *literals 1012 ) 1013 1014 @overload 1015 def add_min_equality( 1016 self, target: LinearExprT, expressions: Iterable[LinearExprT] 1017 ) -> Constraint: ... 1018 1019 @overload 1020 def add_min_equality( 1021 self, target: LinearExprT, *expressions: LinearExprT 1022 ) -> Constraint: ... 1023 1024 def add_min_equality(self, target, *expressions) -> Constraint: 1025 """Adds `target == Min(expressions)`.""" 1026 return self._add_linear_argument_constraint( 1027 cmh.LinearArgumentConstraint.min, target, *expressions 1028 ) 1029 1030 @overload 1031 def add_max_equality( 1032 self, target: LinearExprT, expressions: Iterable[LinearExprT] 1033 ) -> Constraint: ... 1034 1035 @overload 1036 def add_max_equality( 1037 self, target: LinearExprT, *expressions: LinearExprT 1038 ) -> Constraint: ... 1039 1040 def add_max_equality(self, target, *expressions) -> Constraint: 1041 """Adds `target == Max(expressions)`.""" 1042 return self._add_linear_argument_constraint( 1043 cmh.LinearArgumentConstraint.max, target, *expressions 1044 ) 1045 1046 def add_division_equality( 1047 self, target: LinearExprT, num: LinearExprT, denom: LinearExprT 1048 ) -> Constraint: 1049 """Adds `target == num // denom` (integer division rounded towards 0).""" 1050 return self._add_linear_argument_constraint( 1051 cmh.LinearArgumentConstraint.div, target, [num, denom] 1052 ) 1053 1054 def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint: 1055 """Adds `target == Abs(expr)`.""" 1056 return self._add_linear_argument_constraint( 1057 cmh.LinearArgumentConstraint.max, target, [expr, -expr] 1058 ) 1059 1060 def add_modulo_equality( 1061 self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT 1062 ) -> Constraint: 1063 """Adds `target = expr % mod`. 1064 1065 It uses the C convention, that is the result is the remainder of the 1066 integral division rounded towards 0. 1067 1068 For example: 1069 * 10 % 3 = 1 1070 * -10 % 3 = -1 1071 * 10 % -3 = 1 1072 * -10 % -3 = -1 1073 1074 Args: 1075 target: the target expression. 1076 expr: the expression to compute the modulo of. 1077 mod: the modulus expression. 1078 1079 Returns: 1080 A `Constraint` object. 1081 """ 1082 return self._add_linear_argument_constraint( 1083 cmh.LinearArgumentConstraint.mod, target, [expr, mod] 1084 ) 1085 1086 def add_multiplication_equality( 1087 self, 1088 target: LinearExprT, 1089 *expressions: Union[Iterable[LinearExprT], LinearExprT], 1090 ) -> Constraint: 1091 """Adds `target == expressions[0] * .. * expressions[n]`.""" 1092 return self._add_linear_argument_constraint( 1093 cmh.LinearArgumentConstraint.prod, target, *expressions 1094 ) 1095 1096 # Scheduling support 1097 1098 def new_interval_var( 1099 self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str 1100 ) -> IntervalVar: 1101 """Creates an interval variable from start, size, and end. 1102 1103 An interval variable is a constraint, that is itself used in other 1104 constraints like NoOverlap. 1105 1106 Internally, it ensures that `start + size == end`. 1107 1108 Args: 1109 start: The start of the interval. It must be of the form a * var + b. 1110 size: The size of the interval. It must be of the form a * var + b. 1111 end: The end of the interval. It must be of the form a * var + b. 1112 name: The name of the interval variable. 1113 1114 Returns: 1115 An `IntervalVar` object. 1116 """ 1117 return self._new_interval_var(name, start, size, end, []) 1118 1119 def new_interval_var_series( 1120 self, 1121 name: str, 1122 index: pd.Index, 1123 starts: Union[LinearExprT, pd.Series], 1124 sizes: Union[LinearExprT, pd.Series], 1125 ends: Union[LinearExprT, pd.Series], 1126 ) -> pd.Series: 1127 """Creates a series of interval variables with the given name. 1128 1129 Args: 1130 name (str): Required. The name of the variable set. 1131 index (pd.Index): Required. The index to use for the variable set. 1132 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1133 set. If a `pd.Series` is passed in, it will be based on the 1134 corresponding values of the pd.Series. 1135 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the 1136 set. If a `pd.Series` is passed in, it will be based on the 1137 corresponding values of the pd.Series. 1138 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the 1139 set. If a `pd.Series` is passed in, it will be based on the 1140 corresponding values of the pd.Series. 1141 1142 Returns: 1143 pd.Series: The interval variable set indexed by its corresponding 1144 dimensions. 1145 1146 Raises: 1147 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1148 ValueError: if the `name` is not a valid identifier or already exists. 1149 ValueError: if the all the indexes do not match. 1150 """ 1151 if not isinstance(index, pd.Index): 1152 raise TypeError("Non-index object is used as index") 1153 if not name.isidentifier(): 1154 raise ValueError(f"name={name!r} is not a valid identifier") 1155 1156 starts = _convert_to_series_and_validate_index(starts, index) 1157 sizes = _convert_to_series_and_validate_index(sizes, index) 1158 ends = _convert_to_series_and_validate_index(ends, index) 1159 interval_array = [] 1160 for i in index: 1161 interval_array.append( 1162 self.new_interval_var( 1163 start=starts[i], 1164 size=sizes[i], 1165 end=ends[i], 1166 name=f"{name}[{i}]", 1167 ) 1168 ) 1169 return pd.Series(index=index, data=interval_array) 1170 1171 def new_fixed_size_interval_var( 1172 self, start: LinearExprT, size: IntegralT, name: str 1173 ) -> IntervalVar: 1174 """Creates an interval variable from start, and a fixed size. 1175 1176 An interval variable is a constraint, that is itself used in other 1177 constraints like NoOverlap. 1178 1179 Args: 1180 start: The start of the interval. It must be of the form a * var + b. 1181 size: The size of the interval. It must be an integer value. 1182 name: The name of the interval variable. 1183 1184 Returns: 1185 An `IntervalVar` object. 1186 """ 1187 return self._new_interval_var(name, start, size, start + size, []) 1188 1189 def new_fixed_size_interval_var_series( 1190 self, 1191 name: str, 1192 index: pd.Index, 1193 starts: Union[LinearExprT, pd.Series], 1194 sizes: Union[IntegralT, pd.Series], 1195 ) -> pd.Series: 1196 """Creates a series of interval variables with the given name. 1197 1198 Args: 1199 name (str): Required. The name of the variable set. 1200 index (pd.Index): Required. The index to use for the variable set. 1201 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1202 set. If a `pd.Series` is passed in, it will be based on the 1203 corresponding values of the pd.Series. 1204 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in 1205 the set. If a `pd.Series` is passed in, it will be based on the 1206 corresponding values of the pd.Series. 1207 1208 Returns: 1209 pd.Series: The interval variable set indexed by its corresponding 1210 dimensions. 1211 1212 Raises: 1213 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1214 ValueError: if the `name` is not a valid identifier or already exists. 1215 ValueError: if the all the indexes do not match. 1216 """ 1217 if not isinstance(index, pd.Index): 1218 raise TypeError("Non-index object is used as index") 1219 if not name.isidentifier(): 1220 raise ValueError(f"name={name!r} is not a valid identifier") 1221 1222 starts = _convert_to_series_and_validate_index(starts, index) 1223 sizes = _convert_to_series_and_validate_index(sizes, index) 1224 interval_array = [] 1225 for i in index: 1226 interval_array.append( 1227 self.new_fixed_size_interval_var( 1228 start=starts[i], 1229 size=sizes[i], 1230 name=f"{name}[{i}]", 1231 ) 1232 ) 1233 return pd.Series(index=index, data=interval_array) 1234 1235 def new_optional_interval_var( 1236 self, 1237 start: LinearExprT, 1238 size: LinearExprT, 1239 end: LinearExprT, 1240 is_present: LiteralT, 1241 name: str, 1242 ) -> IntervalVar: 1243 """Creates an optional interval var from start, size, end, and is_present. 1244 1245 An optional interval variable is a constraint, that is itself used in other 1246 constraints like NoOverlap. This constraint is protected by a presence 1247 literal that indicates if it is active or not. 1248 1249 Internally, it ensures that `is_present` implies `start + size == 1250 end`. 1251 1252 Args: 1253 start: The start of the interval. It must be of the form a * var + b. 1254 size: The size of the interval. It must be of the form a * var + b. 1255 end: The end of the interval. It must be of the form a * var + b. 1256 is_present: A literal that indicates if the interval is active or not. A 1257 inactive interval is simply ignored by all constraints. 1258 name: The name of the interval variable. 1259 1260 Returns: 1261 An `IntervalVar` object. 1262 """ 1263 return self._new_interval_var( 1264 name, 1265 start, 1266 size, 1267 end, 1268 [is_present], 1269 ) 1270 1271 def new_optional_interval_var_series( 1272 self, 1273 name: str, 1274 index: pd.Index, 1275 starts: Union[LinearExprT, pd.Series], 1276 sizes: Union[LinearExprT, pd.Series], 1277 ends: Union[LinearExprT, pd.Series], 1278 are_present: Union[LiteralT, pd.Series], 1279 ) -> pd.Series: 1280 """Creates a series of interval variables with the given name. 1281 1282 Args: 1283 name (str): Required. The name of the variable set. 1284 index (pd.Index): Required. The index to use for the variable set. 1285 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1286 set. If a `pd.Series` is passed in, it will be based on the 1287 corresponding values of the pd.Series. 1288 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the 1289 set. If a `pd.Series` is passed in, it will be based on the 1290 corresponding values of the pd.Series. 1291 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the 1292 set. If a `pd.Series` is passed in, it will be based on the 1293 corresponding values of the pd.Series. 1294 are_present (Union[LiteralT, pd.Series]): The performed literal of each 1295 interval in the set. If a `pd.Series` is passed in, it will be based on 1296 the corresponding values of the pd.Series. 1297 1298 Returns: 1299 pd.Series: The interval variable set indexed by its corresponding 1300 dimensions. 1301 1302 Raises: 1303 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1304 ValueError: if the `name` is not a valid identifier or already exists. 1305 ValueError: if the all the indexes do not match. 1306 """ 1307 if not isinstance(index, pd.Index): 1308 raise TypeError("Non-index object is used as index") 1309 if not name.isidentifier(): 1310 raise ValueError(f"name={name!r} is not a valid identifier") 1311 1312 starts = _convert_to_series_and_validate_index(starts, index) 1313 sizes = _convert_to_series_and_validate_index(sizes, index) 1314 ends = _convert_to_series_and_validate_index(ends, index) 1315 are_present = _convert_to_series_and_validate_index(are_present, index) 1316 1317 interval_array = [] 1318 for i in index: 1319 interval_array.append( 1320 self.new_optional_interval_var( 1321 start=starts[i], 1322 size=sizes[i], 1323 end=ends[i], 1324 is_present=are_present[i], 1325 name=f"{name}[{i}]", 1326 ) 1327 ) 1328 return pd.Series(index=index, data=interval_array) 1329 1330 def new_optional_fixed_size_interval_var( 1331 self, 1332 start: LinearExprT, 1333 size: IntegralT, 1334 is_present: LiteralT, 1335 name: str, 1336 ) -> IntervalVar: 1337 """Creates an interval variable from start, and a fixed size. 1338 1339 An interval variable is a constraint, that is itself used in other 1340 constraints like NoOverlap. 1341 1342 Args: 1343 start: The start of the interval. It must be of the form a * var + b. 1344 size: The size of the interval. It must be an integer value. 1345 is_present: A literal that indicates if the interval is active or not. A 1346 inactive interval is simply ignored by all constraints. 1347 name: The name of the interval variable. 1348 1349 Returns: 1350 An `IntervalVar` object. 1351 """ 1352 return self._new_interval_var( 1353 name, 1354 start, 1355 size, 1356 start + size, 1357 [is_present], 1358 ) 1359 1360 def new_optional_fixed_size_interval_var_series( 1361 self, 1362 name: str, 1363 index: pd.Index, 1364 starts: Union[LinearExprT, pd.Series], 1365 sizes: Union[IntegralT, pd.Series], 1366 are_present: Union[LiteralT, pd.Series], 1367 ) -> pd.Series: 1368 """Creates a series of interval variables with the given name. 1369 1370 Args: 1371 name (str): Required. The name of the variable set. 1372 index (pd.Index): Required. The index to use for the variable set. 1373 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1374 set. If a `pd.Series` is passed in, it will be based on the 1375 corresponding values of the pd.Series. 1376 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in 1377 the set. If a `pd.Series` is passed in, it will be based on the 1378 corresponding values of the pd.Series. 1379 are_present (Union[LiteralT, pd.Series]): The performed literal of each 1380 interval in the set. If a `pd.Series` is passed in, it will be based on 1381 the corresponding values of the pd.Series. 1382 1383 Returns: 1384 pd.Series: The interval variable set indexed by its corresponding 1385 dimensions. 1386 1387 Raises: 1388 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1389 ValueError: if the `name` is not a valid identifier or already exists. 1390 ValueError: if the all the indexes do not match. 1391 """ 1392 if not isinstance(index, pd.Index): 1393 raise TypeError("Non-index object is used as index") 1394 if not name.isidentifier(): 1395 raise ValueError(f"name={name!r} is not a valid identifier") 1396 1397 starts = _convert_to_series_and_validate_index(starts, index) 1398 sizes = _convert_to_series_and_validate_index(sizes, index) 1399 are_present = _convert_to_series_and_validate_index(are_present, index) 1400 interval_array = [] 1401 for i in index: 1402 interval_array.append( 1403 self.new_optional_fixed_size_interval_var( 1404 start=starts[i], 1405 size=sizes[i], 1406 is_present=are_present[i], 1407 name=f"{name}[{i}]", 1408 ) 1409 ) 1410 return pd.Series(index=index, data=interval_array) 1411 1412 def add_no_overlap(self, intervals: Iterable[IntervalVar]) -> Constraint: 1413 """Adds NoOverlap(interval_vars). 1414 1415 A NoOverlap constraint ensures that all present intervals do not overlap 1416 in time. 1417 1418 Args: 1419 intervals: The list of interval variables to constrain. 1420 1421 Returns: 1422 An instance of the `Constraint` class. 1423 """ 1424 return self._add_no_overlap(intervals) 1425 1426 def add_no_overlap_2d( 1427 self, 1428 x_intervals: Iterable[IntervalVar], 1429 y_intervals: Iterable[IntervalVar], 1430 ) -> Constraint: 1431 """Adds NoOverlap2D(x_intervals, y_intervals). 1432 1433 A NoOverlap2D constraint ensures that all present rectangles do not overlap 1434 on a plane. Each rectangle is aligned with the X and Y axis, and is defined 1435 by two intervals which represent its projection onto the X and Y axis. 1436 1437 Furthermore, one box is optional if at least one of the x or y interval is 1438 optional. 1439 1440 Args: 1441 x_intervals: The X coordinates of the rectangles. 1442 y_intervals: The Y coordinates of the rectangles. 1443 1444 Returns: 1445 An instance of the `Constraint` class. 1446 """ 1447 return self._add_no_overlap_2d(x_intervals, y_intervals) 1448 1449 def add_cumulative( 1450 self, 1451 intervals: Iterable[IntervalVar], 1452 demands: Iterable[LinearExprT], 1453 capacity: LinearExprT, 1454 ) -> Constraint: 1455 """Adds Cumulative(intervals, demands, capacity). 1456 1457 This constraint enforces that: 1458 1459 for all t: 1460 sum(demands[i] 1461 if (start(intervals[i]) <= t < end(intervals[i])) and 1462 (intervals[i] is present)) <= capacity 1463 1464 Args: 1465 intervals: The list of intervals. 1466 demands: The list of demands for each interval. Each demand must be >= 0. 1467 Each demand can be a 1-var affine expression (a * x + b). 1468 capacity: The maximum capacity of the cumulative constraint. It can be a 1469 1-var affine expression (a * x + b). 1470 1471 Returns: 1472 An instance of the `Constraint` class. 1473 """ 1474 return self._add_cumulative(intervals, demands, capacity) 1475 1476 # Support for model cloning. 1477 def clone(self) -> "CpModel": 1478 """Reset the model, and creates a new one from a CpModelProto instance.""" 1479 clone = CpModel() 1480 clone.proto.copy_from(self.proto) 1481 clone.rebuild_constant_map() 1482 return clone 1483 1484 def __copy__(self): 1485 return CpModel(self.model_proto) 1486 1487 def __deepcopy__(self, memo): 1488 return CpModel(copy.deepcopy(self.model_proto, memo)) 1489 1490 def get_bool_var_from_proto_index(self, index: int) -> IntVar: 1491 """Returns an already created Boolean variable from its index.""" 1492 if index < 0 or index >= len(self.model_proto.variables): 1493 raise ValueError( 1494 f"get_bool_var_from_proto_index: out of bound index {index}" 1495 ) 1496 result = IntVar(self.model_proto, index) 1497 if not result.is_boolean: 1498 raise TypeError( 1499 f"get_bool_var_from_proto_index: index {index} does not reference a" 1500 " boolean variable" 1501 ) 1502 return result 1503 1504 def get_int_var_from_proto_index(self, index: int) -> IntVar: 1505 """Returns an already created integer variable from its index.""" 1506 if index < 0 or index >= len(self.model_proto.variables): 1507 raise ValueError( 1508 f"get_int_var_from_proto_index: out of bound index {index}" 1509 ) 1510 return IntVar(self.model_proto, index) 1511 1512 def get_interval_var_from_proto_index(self, index: int) -> IntervalVar: 1513 """Returns an already created interval variable from its index.""" 1514 if index < 0 or index >= len(self.model_proto.constraints): 1515 raise ValueError( 1516 f"get_interval_var_from_proto_index: out of bound index {index}" 1517 ) 1518 ct = self.model_proto.constraints[index] 1519 if not ct.has_interval(): 1520 raise ValueError( 1521 f"get_interval_var_from_proto_index: index {index} does not" 1522 " reference an" + " interval variable" 1523 ) 1524 1525 return IntervalVar(self.model_proto, index) 1526 1527 def __str__(self) -> str: 1528 return str(self.model_proto) 1529 1530 @property 1531 def proto(self) -> cmh.CpModelProto: 1532 """Returns the underlying CpModelProto.""" 1533 return self.model_proto 1534 1535 def negated(self, index: int) -> int: 1536 return -index - 1 1537 1538 def _set_objective(self, obj: ObjLinearExprT, maximize: bool): 1539 """Sets the objective of the model.""" 1540 self.clear_objective() 1541 if isinstance(obj, IntegralTypes): 1542 self.model_proto.objective.offset = int(obj) 1543 self.model_proto.objective.scaling_factor = 1.0 1544 elif isinstance(obj, LinearExpr): 1545 if obj.is_integer(): 1546 int_obj = cmh.FlatIntExpr(obj) 1547 for var in int_obj.vars: 1548 self.model_proto.objective.vars.append(var.index) 1549 if maximize: 1550 self.model_proto.objective.scaling_factor = -1.0 1551 self.model_proto.objective.offset = -int_obj.offset 1552 for c in int_obj.coeffs: 1553 self.model_proto.objective.coeffs.append(-c) 1554 else: 1555 self.model_proto.objective.scaling_factor = 1.0 1556 self.model_proto.objective.offset = int_obj.offset 1557 self.model_proto.objective.coeffs.extend(int_obj.coeffs) 1558 else: 1559 float_obj = cmh.FlatFloatExpr(obj) 1560 for var in float_obj.vars: 1561 self.model_proto.floating_point_objective.vars.append(var.index) 1562 self.model_proto.floating_point_objective.coeffs.extend( 1563 float_obj.coeffs 1564 ) 1565 self.model_proto.floating_point_objective.maximize = maximize 1566 self.model_proto.floating_point_objective.offset = float_obj.offset 1567 else: 1568 raise TypeError( 1569 f"TypeError: {type(obj).__name__!r} is not a valid objective" 1570 ) 1571 1572 def minimize(self, obj: ObjLinearExprT): 1573 """Sets the objective of the model to minimize(obj).""" 1574 self._set_objective(obj, maximize=False) 1575 1576 def maximize(self, obj: ObjLinearExprT): 1577 """Sets the objective of the model to maximize(obj).""" 1578 self._set_objective(obj, maximize=True) 1579 1580 def has_objective(self) -> bool: 1581 return ( 1582 self.model_proto.has_objective() 1583 or self.model_proto.has_floating_point_objective() 1584 ) 1585 1586 def clear_objective(self): 1587 self.model_proto.clear_objective() 1588 self.model_proto.clear_floating_point_objective() 1589 1590 def add_decision_strategy( 1591 self, 1592 variables: Iterable[IntVar], 1593 var_strategy: cmh.DecisionStrategyProto.VariableSelectionStrategy, 1594 domain_strategy: cmh.DecisionStrategyProto.DomainReductionStrategy, 1595 ) -> None: 1596 """Adds a search strategy to the model. 1597 1598 Args: 1599 variables: a list of variables this strategy will assign. 1600 var_strategy: heuristic to choose the next variable to assign. 1601 domain_strategy: heuristic to reduce the domain of the selected variable. 1602 Currently, this is advanced code: the union of all strategies added to 1603 the model must be complete, i.e. instantiates all variables. Otherwise, 1604 solve() will fail. 1605 """ 1606 1607 strategy: cmh.DecisionStrategyProto = self.model_proto.search_strategy.add() 1608 for v in variables: 1609 expr = strategy.exprs.add() 1610 if v.index >= 0: 1611 expr.vars.append(v.index) 1612 expr.coeffs.append(1) 1613 else: 1614 expr.vars.append(self.negated(v.index)) 1615 expr.coeffs.append(-1) 1616 expr.offset = 1 1617 1618 strategy.variable_selection_strategy = var_strategy 1619 strategy.domain_reduction_strategy = domain_strategy 1620 1621 def model_stats(self) -> str: 1622 """Returns a string containing some model statistics.""" 1623 return cmh.CpSatHelper.model_stats(self.model_proto) 1624 1625 def validate(self) -> str: 1626 """Returns a string indicating that the model is invalid.""" 1627 return cmh.CpSatHelper.validate_model(self.model_proto) 1628 1629 def export_to_file(self, file: str) -> bool: 1630 """Write the model as a protocol buffer to 'file'. 1631 1632 Args: 1633 file: file to write the model to. If the filename ends with 'txt', the 1634 model will be written as a text file, otherwise, the binary format will 1635 be used. 1636 1637 Returns: 1638 True if the model was correctly written. 1639 """ 1640 return cmh.CpSatHelper.write_model_to_file(self.model_proto, file) 1641 1642 def remove_all_names(self) -> None: 1643 """Removes all names from the model.""" 1644 self.model_proto.clear_name() 1645 for v in self.model_proto.variables: 1646 v.clear_name() 1647 for c in self.model_proto.constraints: 1648 c.clear_name() 1649 1650 @overload 1651 def add_hint(self, var: IntVar, value: int) -> None: ... 1652 1653 @overload 1654 def add_hint(self, literal: BoolVarT, value: bool) -> None: ... 1655 1656 def add_hint(self, var, value) -> None: 1657 """Adds 'var == value' as a hint to the solver.""" 1658 if var.index >= 0: 1659 self.model_proto.solution_hint.vars.append(var.index) 1660 self.model_proto.solution_hint.values.append(int(value)) 1661 else: 1662 self.model_proto.solution_hint.vars.append(self.negated(var.index)) 1663 self.model_proto.solution_hint.values.append(int(not value)) 1664 1665 def clear_hints(self): 1666 """Removes any solution hint from the model.""" 1667 self.model_proto.clear_solution_hint() 1668 1669 def add_assumption(self, lit: LiteralT) -> None: 1670 """Adds the literal to the model as assumptions.""" 1671 self.model_proto.assumptions.append(self.get_or_make_boolean_index(lit)) 1672 1673 def add_assumptions(self, literals: Iterable[LiteralT]) -> None: 1674 """Adds the literals to the model as assumptions.""" 1675 for lit in literals: 1676 self.add_assumption(lit) 1677 1678 def clear_assumptions(self) -> None: 1679 """Removes all assumptions from the model.""" 1680 self.model_proto.assumptions.clear() 1681 1682 # Compatibility with pre PEP8 1683 # pylint: disable=invalid-name 1684 1685 def _add_pre_pep8_methods(self) -> None: 1686 for method_name in dir(self): 1687 if callable(getattr(self, method_name)) and ( 1688 method_name.startswith("add_") 1689 or method_name.startswith("new_") 1690 or method_name.startswith("clear_") 1691 ): 1692 pre_pep8_name = snake_case_to_camel_case(method_name) 1693 setattr( 1694 self, 1695 pre_pep8_name, 1696 deprecated_method(getattr(self, method_name), pre_pep8_name), 1697 ) 1698 1699 for other_method_name in [ 1700 "add", 1701 "clone", 1702 "get_bool_var_from_proto_index", 1703 "get_int_var_from_proto_index", 1704 "get_interval_var_from_proto_index", 1705 "minimize", 1706 "maximize", 1707 "has_objective", 1708 "model_stats", 1709 "validate", 1710 "export_to_file", 1711 ]: 1712 pre_pep8_name = snake_case_to_camel_case(other_method_name) 1713 setattr( 1714 self, 1715 pre_pep8_name, 1716 deprecated_method(getattr(self, other_method_name), pre_pep8_name), 1717 ) 1718 1719 @deprecated("Use name property instead.") 1720 def Name(self) -> str: 1721 return self.name 1722 1723 @deprecated("Use name property instead.") 1724 def SetName(self, name: str) -> None: 1725 self.name = name 1726 1727 @deprecated("Use proto property instead.") 1728 def Proto(self) -> cmh.CpModelProto: 1729 return self.proto 1730 1731 # 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.
316 def __init__(self, model_proto: Optional[cmh.CpModelProto] = None) -> None: 317 cmh.CpBaseModel.__init__(self, model_proto) 318 self._add_pre_pep8_methods()
__init__(self: ortools.sat.python.cp_model_helper.CpBaseModel, arg0: operations_research::sat::CpModelProto) -> None
321 @property 322 def name(self) -> str: 323 """Returns the name of the model.""" 324 if not self.model_proto or not self.model_proto.name: 325 return "" 326 return self.model_proto.name
Returns the name of the model.
334 def new_int_var(self, lb: IntegralT, ub: IntegralT, name: str) -> IntVar: 335 """Create an integer variable with domain [lb, ub]. 336 337 The CP-SAT solver is limited to integer variables. If you have fractional 338 values, scale them up so that they become integers; if you have strings, 339 encode them as integers. 340 341 Args: 342 lb: Lower bound for the variable. 343 ub: Upper bound for the variable. 344 name: The name of the variable. 345 346 Returns: 347 a variable whose domain is [lb, ub]. 348 """ 349 return ( 350 IntVar(self.model_proto) 351 .with_name(name) 352 .with_domain(sorted_interval_list.Domain(lb, ub)) 353 )
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].
355 def new_int_var_from_domain( 356 self, domain: sorted_interval_list.Domain, name: str 357 ) -> IntVar: 358 """Create an integer variable from a domain. 359 360 A domain is a set of integers specified by a collection of intervals. 361 For example, `model.new_int_var_from_domain(cp_model. 362 Domain.from_intervals([[1, 2], [4, 6]]), 'x')` 363 364 Args: 365 domain: An instance of the Domain class. 366 name: The name of the variable. 367 368 Returns: 369 a variable whose domain is the given domain. 370 """ 371 return IntVar(self.model_proto).with_name(name).with_domain(domain)
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.
373 def new_bool_var(self, name: str) -> IntVar: 374 """Creates a 0-1 variable with the given name.""" 375 return ( 376 IntVar(self.model_proto) 377 .with_name(name) 378 .with_domain(sorted_interval_list.Domain(0, 1)) 379 )
Creates a 0-1 variable with the given name.
381 def new_constant(self, value: IntegralT) -> IntVar: 382 """Declares a constant integer.""" 383 return IntVar(self.model_proto, self.get_or_make_index_from_constant(value))
Declares a constant integer.
385 def new_int_var_series( 386 self, 387 name: str, 388 index: pd.Index, 389 lower_bounds: Union[IntegralT, pd.Series], 390 upper_bounds: Union[IntegralT, pd.Series], 391 ) -> pd.Series: 392 """Creates a series of (scalar-valued) variables with the given name. 393 394 Args: 395 name (str): Required. The name of the variable set. 396 index (pd.Index): Required. The index to use for the variable set. 397 lower_bounds (Union[int, pd.Series]): A lower bound for variables in the 398 set. If a `pd.Series` is passed in, it will be based on the 399 corresponding values of the pd.Series. 400 upper_bounds (Union[int, pd.Series]): An upper bound for variables in the 401 set. If a `pd.Series` is passed in, it will be based on the 402 corresponding values of the pd.Series. 403 404 Returns: 405 pd.Series: The variable set indexed by its corresponding dimensions. 406 407 Raises: 408 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 409 ValueError: if the `name` is not a valid identifier or already exists. 410 ValueError: if the `lowerbound` is greater than the `upperbound`. 411 ValueError: if the index of `lower_bound`, or `upper_bound` does not match 412 the input index. 413 """ 414 if not isinstance(index, pd.Index): 415 raise TypeError("Non-index object is used as index") 416 if not name.isidentifier(): 417 raise ValueError(f"name={name!r} is not a valid identifier") 418 if ( 419 isinstance(lower_bounds, IntegralTypes) 420 and isinstance(upper_bounds, IntegralTypes) 421 and lower_bounds > upper_bounds 422 ): 423 raise ValueError( 424 f"lower_bound={lower_bounds} is greater than" 425 f" upper_bound={upper_bounds} for variable set={name}" 426 ) 427 428 lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index) 429 upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index) 430 return pd.Series( 431 index=index, 432 data=[ 433 # pylint: disable=g-complex-comprehension 434 IntVar(self.model_proto) 435 .with_name(f"{name}[{i}]") 436 .with_domain( 437 sorted_interval_list.Domain(lower_bounds[i], upper_bounds[i]) 438 ) 439 for i in index 440 ], 441 )
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.Seriesis 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.Seriesis 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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the
lowerboundis greater than theupperbound. - ValueError: if the index of
lower_bound, orupper_bounddoes not match - the input index.
443 def new_bool_var_series( 444 self, 445 name: str, 446 index: pd.Index, 447 ) -> pd.Series: 448 """Creates a series of (scalar-valued) variables with the given name. 449 450 Args: 451 name (str): Required. The name of the variable set. 452 index (pd.Index): Required. The index to use for the variable set. 453 454 Returns: 455 pd.Series: The variable set indexed by its corresponding dimensions. 456 457 Raises: 458 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 459 ValueError: if the `name` is not a valid identifier or already exists. 460 """ 461 if not isinstance(index, pd.Index): 462 raise TypeError("Non-index object is used as index") 463 if not name.isidentifier(): 464 raise ValueError(f"name={name!r} is not a valid identifier") 465 return pd.Series( 466 index=index, 467 data=[ 468 # pylint: disable=g-complex-comprehension 469 IntVar(self.model_proto) 470 .with_name(f"{name}[{i}]") 471 .with_domain(sorted_interval_list.Domain(0, 1)) 472 for i in index 473 ], 474 )
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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists.
478 def add_linear_constraint( 479 self, linear_expr: LinearExprT, lb: IntegralT, ub: IntegralT 480 ) -> Constraint: 481 """Adds the constraint: `lb <= linear_expr <= ub`.""" 482 return self.add_linear_expression_in_domain( 483 linear_expr, sorted_interval_list.Domain(lb, ub) 484 )
Adds the constraint: lb <= linear_expr <= ub.
486 def add_linear_expression_in_domain( 487 self, 488 linear_expr: LinearExprT, 489 domain: sorted_interval_list.Domain, 490 ) -> Constraint: 491 """Adds the constraint: `linear_expr` in `domain`.""" 492 if isinstance(linear_expr, LinearExpr): 493 ble = BoundedLinearExpression(linear_expr, domain) 494 if not ble.ok: 495 raise TypeError( 496 "Cannot add a linear expression containing floating point" 497 f" coefficients or constants: {type(linear_expr).__name__!r}" 498 ) 499 return self._add_bounded_linear_expression(ble) 500 if isinstance(linear_expr, IntegralTypes): 501 if not domain.contains(int(linear_expr)): 502 return self.add_bool_or([]) # Evaluate to false. 503 else: 504 return self.add_bool_and([]) # Evaluate to true. 505 raise TypeError( 506 "not supported:" 507 f" CpModel.add_linear_expression_in_domain({type(linear_expr).__name__!r})" 508 )
Adds the constraint: linear_expr in domain.
510 def add(self, ct: Union[BoundedLinearExpression, bool, np.bool_]) -> Constraint: 511 """Adds a `BoundedLinearExpression` to the model. 512 513 Args: 514 ct: A [`BoundedLinearExpression`](#boundedlinearexpression). 515 516 Returns: 517 An instance of the `Constraint` class. 518 519 Raises: 520 TypeError: If the `ct` is not a `BoundedLinearExpression` or a Boolean. 521 """ 522 if isinstance(ct, BoundedLinearExpression): 523 return self._add_bounded_linear_expression(ct) 524 if ct and self.is_boolean_value(ct): 525 return self.add_bool_or([True]) 526 if not ct and self.is_boolean_value(ct): 527 return self.add_bool_or([]) # Evaluate to false. 528 raise TypeError(f"not supported: CpModel.add({type(ct).__name__!r})")
Adds a BoundedLinearExpression to the model.
Arguments:
- ct: A
BoundedLinearExpression.
Returns:
An instance of the
Constraintclass.
Raises:
- TypeError: If the
ctis not aBoundedLinearExpressionor a Boolean.
538 def add_all_different(self, *expressions): 539 """Adds AllDifferent(expressions). 540 541 This constraint forces all expressions to have different values. 542 543 Args: 544 *expressions: simple expressions of the form a * var + constant. 545 546 Returns: 547 An instance of the `Constraint` class. 548 """ 549 return self._add_all_different(*expressions)
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
Constraintclass.
551 def add_element( 552 self, 553 index: LinearExprT, 554 expressions: Sequence[LinearExprT], 555 target: LinearExprT, 556 ) -> Constraint: 557 """Adds the element constraint: `expressions[index] == target`. 558 559 Args: 560 index: The index of the selected expression in the array. It must be an 561 affine expression (a * var + b). 562 expressions: A list of affine expressions. 563 target: The expression constrained to be equal to the selected expression. 564 It must be an affine expression (a * var + b). 565 566 Returns: 567 An instance of the `Constraint` class. 568 """ 569 570 if not expressions: 571 raise ValueError("add_element expects a non-empty expressions array") 572 573 if isinstance(index, IntegralTypes): 574 expression: LinearExprT = list(expressions)[int(index)] 575 return self.add(expression == target) 576 577 return self._add_element(index, expressions, target)
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
Constraintclass.
579 def add_circuit(self, arcs: Sequence[ArcT]) -> Constraint: 580 """Adds Circuit(arcs). 581 582 Adds a circuit constraint from a sparse list of arcs that encode the graph. 583 584 A circuit is a unique Hamiltonian cycle in a subgraph of the total 585 graph. In case a node 'i' is not in the cycle, then there must be a 586 loop arc 'i -> i' associated with a true literal. Otherwise 587 this constraint will fail. 588 589 Args: 590 arcs: a list of arcs. An arc is a tuple (source_node, destination_node, 591 literal). The arc is selected in the circuit if the literal is true. 592 Both source_node and destination_node must be integers between 0 and the 593 number of nodes - 1. 594 595 Returns: 596 An instance of the `Constraint` class. 597 598 Raises: 599 ValueError: If the list of arcs is empty. 600 """ 601 if not arcs: 602 raise ValueError("add_circuit expects a non-empty array of arcs") 603 return self._add_circuit(arcs)
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
Constraintclass.
Raises:
- ValueError: If the list of arcs is empty.
605 def add_multiple_circuit(self, arcs: Sequence[ArcT]) -> Constraint: 606 """Adds a multiple circuit constraint, aka the 'VRP' constraint. 607 608 The direct graph where arc #i (from tails[i] to head[i]) is present iff 609 literals[i] is true must satisfy this set of properties: 610 - #incoming arcs == 1 except for node 0. 611 - #outgoing arcs == 1 except for node 0. 612 - for node zero, #incoming arcs == #outgoing arcs. 613 - There are no duplicate arcs. 614 - Self-arcs are allowed except for node 0. 615 - There is no cycle in this graph, except through node 0. 616 617 Args: 618 arcs: a list of arcs. An arc is a tuple (source_node, destination_node, 619 literal). The arc is selected in the circuit if the literal is true. 620 Both source_node and destination_node must be integers between 0 and the 621 number of nodes - 1. 622 623 Returns: 624 An instance of the `Constraint` class. 625 626 Raises: 627 ValueError: If the list of arcs is empty. 628 """ 629 if not arcs: 630 raise ValueError("add_multiple_circuit expects a non-empty array of arcs") 631 return self._add_routes(arcs)
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
Constraintclass.
Raises:
- ValueError: If the list of arcs is empty.
633 def add_allowed_assignments( 634 self, 635 expressions: Sequence[LinearExprT], 636 tuples_list: Iterable[Sequence[IntegralT]], 637 ) -> Constraint: 638 """Adds AllowedAssignments(expressions, tuples_list). 639 640 An AllowedAssignments constraint is a constraint on an array of affine 641 expressions, which requires that when all expressions are assigned values, 642 the 643 resulting array equals one of the tuples in `tuple_list`. 644 645 Args: 646 expressions: A list of affine expressions (a * var + b). 647 tuples_list: A list of admissible tuples. Each tuple must have the same 648 length as the expressions, and the ith value of a tuple corresponds to 649 the ith expression. 650 651 Returns: 652 An instance of the `Constraint` class. 653 654 Raises: 655 TypeError: If a tuple does not have the same size as the list of 656 expressions. 657 ValueError: If the array of expressions is empty. 658 """ 659 660 if not expressions: 661 raise ValueError( 662 "add_allowed_assignments expects a non-empty expressions array" 663 ) 664 665 return self._add_table(expressions, tuples_list, False)
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
Constraintclass.
Raises:
- TypeError: If a tuple does not have the same size as the list of expressions.
- ValueError: If the array of expressions is empty.
667 def add_forbidden_assignments( 668 self, 669 expressions: Sequence[LinearExprT], 670 tuples_list: Iterable[Sequence[IntegralT]], 671 ) -> Constraint: 672 """Adds add_forbidden_assignments(expressions, [tuples_list]). 673 674 A ForbiddenAssignments constraint is a constraint on an array of affine 675 expressions where the list of impossible combinations is provided in the 676 tuples list. 677 678 Args: 679 expressions: A list of affine expressions (a * var + b). 680 tuples_list: A list of forbidden tuples. Each tuple must have the same 681 length as the expressions, and the *i*th value of a tuple corresponds to 682 the *i*th expression. 683 684 Returns: 685 An instance of the `Constraint` class. 686 687 Raises: 688 TypeError: If a tuple does not have the same size as the list of 689 expressions. 690 ValueError: If the array of expressions is empty. 691 """ 692 693 if not expressions: 694 raise ValueError( 695 "add_forbidden_assignments expects a non-empty expressions array" 696 ) 697 698 return self._add_table(expressions, tuples_list, True)
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
Constraintclass.
Raises:
- TypeError: If a tuple does not have the same size as the list of expressions.
- ValueError: If the array of expressions is empty.
700 def add_automaton( 701 self, 702 transition_expressions: Sequence[LinearExprT], 703 starting_state: IntegralT, 704 final_states: Sequence[IntegralT], 705 transition_triples: Sequence[tuple[IntegralT, IntegralT, IntegralT]], 706 ) -> Constraint: 707 """Adds an automaton constraint. 708 709 An automaton constraint takes a list of affine expressions (a * var + b) (of 710 size *n*), an initial state, a set of final states, and a set of 711 transitions. A transition is a triplet (*tail*, *transition*, *head*), where 712 *tail* and *head* are states, and *transition* is the label of an arc from 713 *head* to *tail*, corresponding to the value of one expression in the list 714 of 715 expressions. 716 717 This automaton will be unrolled into a flow with *n* + 1 phases. Each phase 718 contains the possible states of the automaton. The first state contains the 719 initial state. The last phase contains the final states. 720 721 Between two consecutive phases *i* and *i* + 1, the automaton creates a set 722 of arcs. For each transition (*tail*, *transition*, *head*), it will add 723 an arc from the state *tail* of phase *i* and the state *head* of phase 724 *i* + 1. This arc is labeled by the value *transition* of the expression 725 `expressions[i]`. That is, this arc can only be selected if `expressions[i]` 726 is assigned the value *transition*. 727 728 A feasible solution of this constraint is an assignment of expressions such 729 that, starting from the initial state in phase 0, there is a path labeled by 730 the values of the expressions that ends in one of the final states in the 731 final phase. 732 733 Args: 734 transition_expressions: A non-empty list of affine expressions (a * var + 735 b) whose values correspond to the labels of the arcs traversed by the 736 automaton. 737 starting_state: The initial state of the automaton. 738 final_states: A non-empty list of admissible final states. 739 transition_triples: A list of transitions for the automaton, in the 740 following format (current_state, variable_value, next_state). 741 742 Returns: 743 An instance of the `Constraint` class. 744 745 Raises: 746 ValueError: if `transition_expressions`, `final_states`, or 747 `transition_triples` are empty. 748 """ 749 750 if not transition_expressions: 751 raise ValueError( 752 "add_automaton expects a non-empty transition_expressions array" 753 ) 754 if not final_states: 755 raise ValueError("add_automaton expects some final states") 756 757 if not transition_triples: 758 raise ValueError("add_automaton expects some transition triples") 759 760 return self._add_automaton( 761 transition_expressions, 762 starting_state, 763 final_states, 764 transition_triples, 765 )
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
Constraintclass.
Raises:
- ValueError: if
transition_expressions,final_states, ortransition_triplesare empty.
767 def add_inverse( 768 self, 769 variables: Sequence[VariableT], 770 inverse_variables: Sequence[VariableT], 771 ) -> Constraint: 772 """Adds Inverse(variables, inverse_variables). 773 774 An inverse constraint enforces that if `variables[i]` is assigned a value 775 `j`, then `inverse_variables[j]` is assigned a value `i`. And vice versa. 776 777 Args: 778 variables: An array of integer variables. 779 inverse_variables: An array of integer variables. 780 781 Returns: 782 An instance of the `Constraint` class. 783 784 Raises: 785 TypeError: if variables and inverse_variables have different lengths, or 786 if they are empty. 787 """ 788 789 if not variables or not inverse_variables: 790 raise TypeError("The Inverse constraint does not accept empty arrays") 791 if len(variables) != len(inverse_variables): 792 raise TypeError( 793 "In the inverse constraint, the two array variables and" 794 " inverse_variables must have the same length." 795 ) 796 return self._add_inverse(variables, inverse_variables)
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
Constraintclass.
Raises:
- TypeError: if variables and inverse_variables have different lengths, or if they are empty.
798 def add_reservoir_constraint( 799 self, 800 times: Sequence[LinearExprT], 801 level_changes: Sequence[LinearExprT], 802 min_level: int, 803 max_level: int, 804 ) -> Constraint: 805 """Adds Reservoir(times, level_changes, min_level, max_level). 806 807 Maintains a reservoir level within bounds. The water level starts at 0, and 808 at any time, it must be between min_level and max_level. 809 810 If the affine expression `times[i]` is assigned a value t, then the current 811 level changes by `level_changes[i]`, which is constant, at time t. 812 813 Note that min level must be <= 0, and the max level must be >= 0. Please 814 use fixed level_changes to simulate initial state. 815 816 Therefore, at any time: 817 sum(level_changes[i] if times[i] <= t) in [min_level, max_level] 818 819 Args: 820 times: A list of 1-var affine expressions (a * x + b) which specify the 821 time of the filling or emptying the reservoir. 822 level_changes: A list of integer values that specifies the amount of the 823 emptying or filling. Currently, variable demands are not supported. 824 min_level: At any time, the level of the reservoir must be greater or 825 equal than the min level. 826 max_level: At any time, the level of the reservoir must be less or equal 827 than the max level. 828 829 Returns: 830 An instance of the `Constraint` class. 831 832 Raises: 833 ValueError: if max_level < min_level. 834 835 ValueError: if max_level < 0. 836 837 ValueError: if min_level > 0 838 """ 839 840 return self._add_reservoir( 841 times, 842 level_changes, 843 [], 844 min_level, 845 max_level, 846 )
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
Constraintclass.
Raises:
- ValueError: if max_level < min_level.
- ValueError: if max_level < 0.
- ValueError: if min_level > 0
848 def add_reservoir_constraint_with_active( 849 self, 850 times: Sequence[LinearExprT], 851 level_changes: Sequence[LinearExprT], 852 actives: Sequence[LiteralT], 853 min_level: int, 854 max_level: int, 855 ) -> Constraint: 856 """Adds Reservoir(times, level_changes, actives, min_level, max_level). 857 858 Maintains a reservoir level within bounds. The water level starts at 0, and 859 at any time, it must be between min_level and max_level. 860 861 If the variable `times[i]` is assigned a value t, and `actives[i]` is 862 `True`, then the current level changes by `level_changes[i]`, which is 863 constant, 864 at time t. 865 866 Note that min level must be <= 0, and the max level must be >= 0. Please 867 use fixed level_changes to simulate initial state. 868 869 Therefore, at any time: 870 sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level, 871 max_level] 872 873 874 The array of boolean variables 'actives', if defined, indicates which 875 actions are actually performed. 876 877 Args: 878 times: A list of 1-var affine expressions (a * x + b) which specify the 879 time of the filling or emptying the reservoir. 880 level_changes: A list of integer values that specifies the amount of the 881 emptying or filling. Currently, variable demands are not supported. 882 actives: a list of boolean variables. They indicates if the 883 emptying/refilling events actually take place. 884 min_level: At any time, the level of the reservoir must be greater or 885 equal than the min level. 886 max_level: At any time, the level of the reservoir must be less or equal 887 than the max level. 888 889 Returns: 890 An instance of the `Constraint` class. 891 892 Raises: 893 ValueError: if max_level < min_level. 894 895 ValueError: if max_level < 0. 896 897 ValueError: if min_level > 0 898 """ 899 900 if max_level < min_level: 901 raise ValueError("Reservoir constraint must have a max_level >= min_level") 902 903 if max_level < 0: 904 raise ValueError("Reservoir constraint must have a max_level >= 0") 905 906 if min_level > 0: 907 raise ValueError("Reservoir constraint must have a min_level <= 0") 908 909 if not times: 910 raise ValueError("Reservoir constraint must have a non-empty times array") 911 912 return self._add_reservoir( 913 times, 914 level_changes, 915 actives, 916 min_level, 917 max_level, 918 )
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
Constraintclass.
Raises:
- ValueError: if max_level < min_level.
- ValueError: if max_level < 0.
- ValueError: if min_level > 0
920 def add_map_domain( 921 self, var: IntVar, bool_var_array: Iterable[IntVar], offset: IntegralT = 0 922 ): 923 """Adds `var == i + offset <=> bool_var_array[i] == true for all i`.""" 924 for i, bool_var in enumerate(bool_var_array): 925 self.add(var == i + offset).only_enforce_if(bool_var) 926 self.add(var != i + offset).only_enforce_if(~bool_var)
Adds var == i + offset <=> bool_var_array[i] == true for all i.
928 def add_implication(self, a: LiteralT, b: LiteralT) -> Constraint: 929 """Adds `a => b` (`a` implies `b`).""" 930 return self.add_bool_and(b).only_enforce_if(a)
Adds a => b (a implies b).
938 def add_bool_or(self, *literals): 939 """Adds `Or(literals) == true`: sum(literals) >= 1.""" 940 return self._add_bool_argument_constraint( 941 cmh.BoolArgumentConstraint.bool_or, *literals 942 )
Adds Or(literals) == true: sum(literals) >= 1.
950 def add_at_least_one(self, *literals): 951 """Same as `add_bool_or`: `sum(literals) >= 1`.""" 952 return self._add_bool_argument_constraint( 953 cmh.BoolArgumentConstraint.bool_or, *literals 954 )
Same as add_bool_or: sum(literals) >= 1.
962 def add_at_most_one(self, *literals) -> Constraint: 963 """Adds `AtMostOne(literals)`: `sum(literals) <= 1`.""" 964 return self._add_bool_argument_constraint( 965 cmh.BoolArgumentConstraint.at_most_one, *literals 966 )
Adds AtMostOne(literals): sum(literals) <= 1.
974 def add_exactly_one(self, *literals): 975 """Adds `ExactlyOne(literals)`: `sum(literals) == 1`.""" 976 return self._add_bool_argument_constraint( 977 cmh.BoolArgumentConstraint.exactly_one, *literals 978 )
Adds ExactlyOne(literals): sum(literals) == 1.
986 def add_bool_and(self, *literals): 987 """Adds `And(literals) == true`.""" 988 return self._add_bool_argument_constraint( 989 cmh.BoolArgumentConstraint.bool_and, *literals 990 )
Adds And(literals) == true.
998 def add_bool_xor(self, *literals): 999 """Adds `XOr(literals) == true`. 1000 1001 In contrast to add_bool_or and add_bool_and, it does not support 1002 .only_enforce_if(). 1003 1004 Args: 1005 *literals: the list of literals in the constraint. 1006 1007 Returns: 1008 An `Constraint` object. 1009 """ 1010 return self._add_bool_argument_constraint( 1011 cmh.BoolArgumentConstraint.bool_xor, *literals 1012 )
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
Constraintobject.
1024 def add_min_equality(self, target, *expressions) -> Constraint: 1025 """Adds `target == Min(expressions)`.""" 1026 return self._add_linear_argument_constraint( 1027 cmh.LinearArgumentConstraint.min, target, *expressions 1028 )
Adds target == Min(expressions).
1040 def add_max_equality(self, target, *expressions) -> Constraint: 1041 """Adds `target == Max(expressions)`.""" 1042 return self._add_linear_argument_constraint( 1043 cmh.LinearArgumentConstraint.max, target, *expressions 1044 )
Adds target == Max(expressions).
1046 def add_division_equality( 1047 self, target: LinearExprT, num: LinearExprT, denom: LinearExprT 1048 ) -> Constraint: 1049 """Adds `target == num // denom` (integer division rounded towards 0).""" 1050 return self._add_linear_argument_constraint( 1051 cmh.LinearArgumentConstraint.div, target, [num, denom] 1052 )
Adds target == num // denom (integer division rounded towards 0).
1054 def add_abs_equality(self, target: LinearExprT, expr: LinearExprT) -> Constraint: 1055 """Adds `target == Abs(expr)`.""" 1056 return self._add_linear_argument_constraint( 1057 cmh.LinearArgumentConstraint.max, target, [expr, -expr] 1058 )
Adds target == Abs(expr).
1060 def add_modulo_equality( 1061 self, target: LinearExprT, expr: LinearExprT, mod: LinearExprT 1062 ) -> Constraint: 1063 """Adds `target = expr % mod`. 1064 1065 It uses the C convention, that is the result is the remainder of the 1066 integral division rounded towards 0. 1067 1068 For example: 1069 * 10 % 3 = 1 1070 * -10 % 3 = -1 1071 * 10 % -3 = 1 1072 * -10 % -3 = -1 1073 1074 Args: 1075 target: the target expression. 1076 expr: the expression to compute the modulo of. 1077 mod: the modulus expression. 1078 1079 Returns: 1080 A `Constraint` object. 1081 """ 1082 return self._add_linear_argument_constraint( 1083 cmh.LinearArgumentConstraint.mod, target, [expr, mod] 1084 )
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
Constraintobject.
1086 def add_multiplication_equality( 1087 self, 1088 target: LinearExprT, 1089 *expressions: Union[Iterable[LinearExprT], LinearExprT], 1090 ) -> Constraint: 1091 """Adds `target == expressions[0] * .. * expressions[n]`.""" 1092 return self._add_linear_argument_constraint( 1093 cmh.LinearArgumentConstraint.prod, target, *expressions 1094 )
Adds target == expressions[0] * .. * expressions[n].
1098 def new_interval_var( 1099 self, start: LinearExprT, size: LinearExprT, end: LinearExprT, name: str 1100 ) -> IntervalVar: 1101 """Creates an interval variable from start, size, and end. 1102 1103 An interval variable is a constraint, that is itself used in other 1104 constraints like NoOverlap. 1105 1106 Internally, it ensures that `start + size == end`. 1107 1108 Args: 1109 start: The start of the interval. It must be of the form a * var + b. 1110 size: The size of the interval. It must be of the form a * var + b. 1111 end: The end of the interval. It must be of the form a * var + b. 1112 name: The name of the interval variable. 1113 1114 Returns: 1115 An `IntervalVar` object. 1116 """ 1117 return self._new_interval_var(name, start, size, end, [])
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
IntervalVarobject.
1119 def new_interval_var_series( 1120 self, 1121 name: str, 1122 index: pd.Index, 1123 starts: Union[LinearExprT, pd.Series], 1124 sizes: Union[LinearExprT, pd.Series], 1125 ends: Union[LinearExprT, pd.Series], 1126 ) -> pd.Series: 1127 """Creates a series of interval variables with the given name. 1128 1129 Args: 1130 name (str): Required. The name of the variable set. 1131 index (pd.Index): Required. The index to use for the variable set. 1132 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1133 set. If a `pd.Series` is passed in, it will be based on the 1134 corresponding values of the pd.Series. 1135 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the 1136 set. If a `pd.Series` is passed in, it will be based on the 1137 corresponding values of the pd.Series. 1138 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the 1139 set. If a `pd.Series` is passed in, it will be based on the 1140 corresponding values of the pd.Series. 1141 1142 Returns: 1143 pd.Series: The interval variable set indexed by its corresponding 1144 dimensions. 1145 1146 Raises: 1147 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1148 ValueError: if the `name` is not a valid identifier or already exists. 1149 ValueError: if the all the indexes do not match. 1150 """ 1151 if not isinstance(index, pd.Index): 1152 raise TypeError("Non-index object is used as index") 1153 if not name.isidentifier(): 1154 raise ValueError(f"name={name!r} is not a valid identifier") 1155 1156 starts = _convert_to_series_and_validate_index(starts, index) 1157 sizes = _convert_to_series_and_validate_index(sizes, index) 1158 ends = _convert_to_series_and_validate_index(ends, index) 1159 interval_array = [] 1160 for i in index: 1161 interval_array.append( 1162 self.new_interval_var( 1163 start=starts[i], 1164 size=sizes[i], 1165 end=ends[i], 1166 name=f"{name}[{i}]", 1167 ) 1168 ) 1169 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.Seriesis 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.Seriesis 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.Seriesis 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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the all the indexes do not match.
1171 def new_fixed_size_interval_var( 1172 self, start: LinearExprT, size: IntegralT, name: str 1173 ) -> IntervalVar: 1174 """Creates an interval variable from start, and a fixed size. 1175 1176 An interval variable is a constraint, that is itself used in other 1177 constraints like NoOverlap. 1178 1179 Args: 1180 start: The start of the interval. It must be of the form a * var + b. 1181 size: The size of the interval. It must be an integer value. 1182 name: The name of the interval variable. 1183 1184 Returns: 1185 An `IntervalVar` object. 1186 """ 1187 return self._new_interval_var(name, start, size, start + size, [])
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
IntervalVarobject.
1189 def new_fixed_size_interval_var_series( 1190 self, 1191 name: str, 1192 index: pd.Index, 1193 starts: Union[LinearExprT, pd.Series], 1194 sizes: Union[IntegralT, pd.Series], 1195 ) -> pd.Series: 1196 """Creates a series of interval variables with the given name. 1197 1198 Args: 1199 name (str): Required. The name of the variable set. 1200 index (pd.Index): Required. The index to use for the variable set. 1201 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1202 set. If a `pd.Series` is passed in, it will be based on the 1203 corresponding values of the pd.Series. 1204 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in 1205 the set. If a `pd.Series` is passed in, it will be based on the 1206 corresponding values of the pd.Series. 1207 1208 Returns: 1209 pd.Series: The interval variable set indexed by its corresponding 1210 dimensions. 1211 1212 Raises: 1213 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1214 ValueError: if the `name` is not a valid identifier or already exists. 1215 ValueError: if the all the indexes do not match. 1216 """ 1217 if not isinstance(index, pd.Index): 1218 raise TypeError("Non-index object is used as index") 1219 if not name.isidentifier(): 1220 raise ValueError(f"name={name!r} is not a valid identifier") 1221 1222 starts = _convert_to_series_and_validate_index(starts, index) 1223 sizes = _convert_to_series_and_validate_index(sizes, index) 1224 interval_array = [] 1225 for i in index: 1226 interval_array.append( 1227 self.new_fixed_size_interval_var( 1228 start=starts[i], 1229 size=sizes[i], 1230 name=f"{name}[{i}]", 1231 ) 1232 ) 1233 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.Seriesis 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.Seriesis 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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the all the indexes do not match.
1235 def new_optional_interval_var( 1236 self, 1237 start: LinearExprT, 1238 size: LinearExprT, 1239 end: LinearExprT, 1240 is_present: LiteralT, 1241 name: str, 1242 ) -> IntervalVar: 1243 """Creates an optional interval var from start, size, end, and is_present. 1244 1245 An optional interval variable is a constraint, that is itself used in other 1246 constraints like NoOverlap. This constraint is protected by a presence 1247 literal that indicates if it is active or not. 1248 1249 Internally, it ensures that `is_present` implies `start + size == 1250 end`. 1251 1252 Args: 1253 start: The start of the interval. It must be of the form a * var + b. 1254 size: The size of the interval. It must be of the form a * var + b. 1255 end: The end of the interval. It must be of the form a * var + b. 1256 is_present: A literal that indicates if the interval is active or not. A 1257 inactive interval is simply ignored by all constraints. 1258 name: The name of the interval variable. 1259 1260 Returns: 1261 An `IntervalVar` object. 1262 """ 1263 return self._new_interval_var( 1264 name, 1265 start, 1266 size, 1267 end, 1268 [is_present], 1269 )
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
IntervalVarobject.
1271 def new_optional_interval_var_series( 1272 self, 1273 name: str, 1274 index: pd.Index, 1275 starts: Union[LinearExprT, pd.Series], 1276 sizes: Union[LinearExprT, pd.Series], 1277 ends: Union[LinearExprT, pd.Series], 1278 are_present: Union[LiteralT, pd.Series], 1279 ) -> pd.Series: 1280 """Creates a series of interval variables with the given name. 1281 1282 Args: 1283 name (str): Required. The name of the variable set. 1284 index (pd.Index): Required. The index to use for the variable set. 1285 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1286 set. If a `pd.Series` is passed in, it will be based on the 1287 corresponding values of the pd.Series. 1288 sizes (Union[LinearExprT, pd.Series]): The size of each interval in the 1289 set. If a `pd.Series` is passed in, it will be based on the 1290 corresponding values of the pd.Series. 1291 ends (Union[LinearExprT, pd.Series]): The ends of each interval in the 1292 set. If a `pd.Series` is passed in, it will be based on the 1293 corresponding values of the pd.Series. 1294 are_present (Union[LiteralT, pd.Series]): The performed literal of each 1295 interval in the set. If a `pd.Series` is passed in, it will be based on 1296 the corresponding values of the pd.Series. 1297 1298 Returns: 1299 pd.Series: The interval variable set indexed by its corresponding 1300 dimensions. 1301 1302 Raises: 1303 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1304 ValueError: if the `name` is not a valid identifier or already exists. 1305 ValueError: if the all the indexes do not match. 1306 """ 1307 if not isinstance(index, pd.Index): 1308 raise TypeError("Non-index object is used as index") 1309 if not name.isidentifier(): 1310 raise ValueError(f"name={name!r} is not a valid identifier") 1311 1312 starts = _convert_to_series_and_validate_index(starts, index) 1313 sizes = _convert_to_series_and_validate_index(sizes, index) 1314 ends = _convert_to_series_and_validate_index(ends, index) 1315 are_present = _convert_to_series_and_validate_index(are_present, index) 1316 1317 interval_array = [] 1318 for i in index: 1319 interval_array.append( 1320 self.new_optional_interval_var( 1321 start=starts[i], 1322 size=sizes[i], 1323 end=ends[i], 1324 is_present=are_present[i], 1325 name=f"{name}[{i}]", 1326 ) 1327 ) 1328 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.Seriesis 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.Seriesis 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.Seriesis 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.Seriesis 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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the all the indexes do not match.
1330 def new_optional_fixed_size_interval_var( 1331 self, 1332 start: LinearExprT, 1333 size: IntegralT, 1334 is_present: LiteralT, 1335 name: str, 1336 ) -> IntervalVar: 1337 """Creates an interval variable from start, and a fixed size. 1338 1339 An interval variable is a constraint, that is itself used in other 1340 constraints like NoOverlap. 1341 1342 Args: 1343 start: The start of the interval. It must be of the form a * var + b. 1344 size: The size of the interval. It must be an integer value. 1345 is_present: A literal that indicates if the interval is active or not. A 1346 inactive interval is simply ignored by all constraints. 1347 name: The name of the interval variable. 1348 1349 Returns: 1350 An `IntervalVar` object. 1351 """ 1352 return self._new_interval_var( 1353 name, 1354 start, 1355 size, 1356 start + size, 1357 [is_present], 1358 )
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
IntervalVarobject.
1360 def new_optional_fixed_size_interval_var_series( 1361 self, 1362 name: str, 1363 index: pd.Index, 1364 starts: Union[LinearExprT, pd.Series], 1365 sizes: Union[IntegralT, pd.Series], 1366 are_present: Union[LiteralT, pd.Series], 1367 ) -> pd.Series: 1368 """Creates a series of interval variables with the given name. 1369 1370 Args: 1371 name (str): Required. The name of the variable set. 1372 index (pd.Index): Required. The index to use for the variable set. 1373 starts (Union[LinearExprT, pd.Series]): The start of each interval in the 1374 set. If a `pd.Series` is passed in, it will be based on the 1375 corresponding values of the pd.Series. 1376 sizes (Union[IntegralT, pd.Series]): The fixed size of each interval in 1377 the set. If a `pd.Series` is passed in, it will be based on the 1378 corresponding values of the pd.Series. 1379 are_present (Union[LiteralT, pd.Series]): The performed literal of each 1380 interval in the set. If a `pd.Series` is passed in, it will be based on 1381 the corresponding values of the pd.Series. 1382 1383 Returns: 1384 pd.Series: The interval variable set indexed by its corresponding 1385 dimensions. 1386 1387 Raises: 1388 TypeError: if the `index` is invalid (e.g. a `DataFrame`). 1389 ValueError: if the `name` is not a valid identifier or already exists. 1390 ValueError: if the all the indexes do not match. 1391 """ 1392 if not isinstance(index, pd.Index): 1393 raise TypeError("Non-index object is used as index") 1394 if not name.isidentifier(): 1395 raise ValueError(f"name={name!r} is not a valid identifier") 1396 1397 starts = _convert_to_series_and_validate_index(starts, index) 1398 sizes = _convert_to_series_and_validate_index(sizes, index) 1399 are_present = _convert_to_series_and_validate_index(are_present, index) 1400 interval_array = [] 1401 for i in index: 1402 interval_array.append( 1403 self.new_optional_fixed_size_interval_var( 1404 start=starts[i], 1405 size=sizes[i], 1406 is_present=are_present[i], 1407 name=f"{name}[{i}]", 1408 ) 1409 ) 1410 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.Seriesis 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.Seriesis 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.Seriesis 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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the all the indexes do not match.
1412 def add_no_overlap(self, intervals: Iterable[IntervalVar]) -> Constraint: 1413 """Adds NoOverlap(interval_vars). 1414 1415 A NoOverlap constraint ensures that all present intervals do not overlap 1416 in time. 1417 1418 Args: 1419 intervals: The list of interval variables to constrain. 1420 1421 Returns: 1422 An instance of the `Constraint` class. 1423 """ 1424 return self._add_no_overlap(intervals)
Adds NoOverlap(interval_vars).
A NoOverlap constraint ensures that all present intervals do not overlap in time.
Arguments:
- intervals: The list of interval variables to constrain.
Returns:
An instance of the
Constraintclass.
1426 def add_no_overlap_2d( 1427 self, 1428 x_intervals: Iterable[IntervalVar], 1429 y_intervals: Iterable[IntervalVar], 1430 ) -> Constraint: 1431 """Adds NoOverlap2D(x_intervals, y_intervals). 1432 1433 A NoOverlap2D constraint ensures that all present rectangles do not overlap 1434 on a plane. Each rectangle is aligned with the X and Y axis, and is defined 1435 by two intervals which represent its projection onto the X and Y axis. 1436 1437 Furthermore, one box is optional if at least one of the x or y interval is 1438 optional. 1439 1440 Args: 1441 x_intervals: The X coordinates of the rectangles. 1442 y_intervals: The Y coordinates of the rectangles. 1443 1444 Returns: 1445 An instance of the `Constraint` class. 1446 """ 1447 return self._add_no_overlap_2d(x_intervals, y_intervals)
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
Constraintclass.
1449 def add_cumulative( 1450 self, 1451 intervals: Iterable[IntervalVar], 1452 demands: Iterable[LinearExprT], 1453 capacity: LinearExprT, 1454 ) -> Constraint: 1455 """Adds Cumulative(intervals, demands, capacity). 1456 1457 This constraint enforces that: 1458 1459 for all t: 1460 sum(demands[i] 1461 if (start(intervals[i]) <= t < end(intervals[i])) and 1462 (intervals[i] is present)) <= capacity 1463 1464 Args: 1465 intervals: The list of intervals. 1466 demands: The list of demands for each interval. Each demand must be >= 0. 1467 Each demand can be a 1-var affine expression (a * x + b). 1468 capacity: The maximum capacity of the cumulative constraint. It can be a 1469 1-var affine expression (a * x + b). 1470 1471 Returns: 1472 An instance of the `Constraint` class. 1473 """ 1474 return self._add_cumulative(intervals, demands, capacity)
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
Constraintclass.
1477 def clone(self) -> "CpModel": 1478 """Reset the model, and creates a new one from a CpModelProto instance.""" 1479 clone = CpModel() 1480 clone.proto.copy_from(self.proto) 1481 clone.rebuild_constant_map() 1482 return clone
Reset the model, and creates a new one from a CpModelProto instance.
1490 def get_bool_var_from_proto_index(self, index: int) -> IntVar: 1491 """Returns an already created Boolean variable from its index.""" 1492 if index < 0 or index >= len(self.model_proto.variables): 1493 raise ValueError( 1494 f"get_bool_var_from_proto_index: out of bound index {index}" 1495 ) 1496 result = IntVar(self.model_proto, index) 1497 if not result.is_boolean: 1498 raise TypeError( 1499 f"get_bool_var_from_proto_index: index {index} does not reference a" 1500 " boolean variable" 1501 ) 1502 return result
Returns an already created Boolean variable from its index.
1504 def get_int_var_from_proto_index(self, index: int) -> IntVar: 1505 """Returns an already created integer variable from its index.""" 1506 if index < 0 or index >= len(self.model_proto.variables): 1507 raise ValueError( 1508 f"get_int_var_from_proto_index: out of bound index {index}" 1509 ) 1510 return IntVar(self.model_proto, index)
Returns an already created integer variable from its index.
1512 def get_interval_var_from_proto_index(self, index: int) -> IntervalVar: 1513 """Returns an already created interval variable from its index.""" 1514 if index < 0 or index >= len(self.model_proto.constraints): 1515 raise ValueError( 1516 f"get_interval_var_from_proto_index: out of bound index {index}" 1517 ) 1518 ct = self.model_proto.constraints[index] 1519 if not ct.has_interval(): 1520 raise ValueError( 1521 f"get_interval_var_from_proto_index: index {index} does not" 1522 " reference an" + " interval variable" 1523 ) 1524 1525 return IntervalVar(self.model_proto, index)
Returns an already created interval variable from its index.
1530 @property 1531 def proto(self) -> cmh.CpModelProto: 1532 """Returns the underlying CpModelProto.""" 1533 return self.model_proto
Returns the underlying CpModelProto.
1572 def minimize(self, obj: ObjLinearExprT): 1573 """Sets the objective of the model to minimize(obj).""" 1574 self._set_objective(obj, maximize=False)
Sets the objective of the model to minimize(obj).
1576 def maximize(self, obj: ObjLinearExprT): 1577 """Sets the objective of the model to maximize(obj).""" 1578 self._set_objective(obj, maximize=True)
Sets the objective of the model to maximize(obj).
1590 def add_decision_strategy( 1591 self, 1592 variables: Iterable[IntVar], 1593 var_strategy: cmh.DecisionStrategyProto.VariableSelectionStrategy, 1594 domain_strategy: cmh.DecisionStrategyProto.DomainReductionStrategy, 1595 ) -> None: 1596 """Adds a search strategy to the model. 1597 1598 Args: 1599 variables: a list of variables this strategy will assign. 1600 var_strategy: heuristic to choose the next variable to assign. 1601 domain_strategy: heuristic to reduce the domain of the selected variable. 1602 Currently, this is advanced code: the union of all strategies added to 1603 the model must be complete, i.e. instantiates all variables. Otherwise, 1604 solve() will fail. 1605 """ 1606 1607 strategy: cmh.DecisionStrategyProto = self.model_proto.search_strategy.add() 1608 for v in variables: 1609 expr = strategy.exprs.add() 1610 if v.index >= 0: 1611 expr.vars.append(v.index) 1612 expr.coeffs.append(1) 1613 else: 1614 expr.vars.append(self.negated(v.index)) 1615 expr.coeffs.append(-1) 1616 expr.offset = 1 1617 1618 strategy.variable_selection_strategy = var_strategy 1619 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.
1621 def model_stats(self) -> str: 1622 """Returns a string containing some model statistics.""" 1623 return cmh.CpSatHelper.model_stats(self.model_proto)
Returns a string containing some model statistics.
1625 def validate(self) -> str: 1626 """Returns a string indicating that the model is invalid.""" 1627 return cmh.CpSatHelper.validate_model(self.model_proto)
Returns a string indicating that the model is invalid.
1629 def export_to_file(self, file: str) -> bool: 1630 """Write the model as a protocol buffer to 'file'. 1631 1632 Args: 1633 file: file to write the model to. If the filename ends with 'txt', the 1634 model will be written as a text file, otherwise, the binary format will 1635 be used. 1636 1637 Returns: 1638 True if the model was correctly written. 1639 """ 1640 return cmh.CpSatHelper.write_model_to_file(self.model_proto, 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.
1642 def remove_all_names(self) -> None: 1643 """Removes all names from the model.""" 1644 self.model_proto.clear_name() 1645 for v in self.model_proto.variables: 1646 v.clear_name() 1647 for c in self.model_proto.constraints: 1648 c.clear_name()
Removes all names from the model.
1656 def add_hint(self, var, value) -> None: 1657 """Adds 'var == value' as a hint to the solver.""" 1658 if var.index >= 0: 1659 self.model_proto.solution_hint.vars.append(var.index) 1660 self.model_proto.solution_hint.values.append(int(value)) 1661 else: 1662 self.model_proto.solution_hint.vars.append(self.negated(var.index)) 1663 self.model_proto.solution_hint.values.append(int(not value))
Adds 'var == value' as a hint to the solver.
1665 def clear_hints(self): 1666 """Removes any solution hint from the model.""" 1667 self.model_proto.clear_solution_hint()
Removes any solution hint from the model.
1669 def add_assumption(self, lit: LiteralT) -> None: 1670 """Adds the literal to the model as assumptions.""" 1671 self.model_proto.assumptions.append(self.get_or_make_boolean_index(lit))
Adds the literal to the model as assumptions.
1673 def add_assumptions(self, literals: Iterable[LiteralT]) -> None: 1674 """Adds the literals to the model as assumptions.""" 1675 for lit in literals: 1676 self.add_assumption(lit)
Adds the literals to the model as assumptions.
1678 def clear_assumptions(self) -> None: 1679 """Removes all assumptions from the model.""" 1680 self.model_proto.assumptions.clear()
Removes all assumptions from the model.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
1734class CpSolver: 1735 """Main solver class. 1736 1737 The purpose of this class is to search for a solution to the model provided 1738 to the solve() method. 1739 1740 Once solve() is called, this class allows inspecting the solution found 1741 with the value() and boolean_value() methods, as well as general statistics 1742 about the solve procedure. 1743 """ 1744 1745 def __init__(self) -> None: 1746 self.__response: Optional[cmh.CpSolverResponse] = None 1747 self.parameters: cmh.SatParameters = cmh.SatParameters() 1748 self.log_callback: Optional[Callable[[str], None]] = None 1749 self.best_bound_callback: Optional[Callable[[float], None]] = None 1750 self.__solve_wrapper: Optional[cmh.SolveWrapper] = None 1751 self.__lock: threading.Lock = threading.Lock() 1752 1753 def solve( 1754 self, 1755 model: CpModel, 1756 solution_callback: Optional["CpSolverSolutionCallback"] = None, 1757 ) -> cmh.CpSolverStatus: 1758 """Solves a problem and passes each solution to the callback if not null.""" 1759 with self.__lock: 1760 self.__solve_wrapper = cmh.SolveWrapper() 1761 1762 self.__solve_wrapper.set_parameters(self.parameters) 1763 if solution_callback is not None: 1764 self.__solve_wrapper.add_solution_callback(solution_callback) 1765 1766 if self.log_callback is not None: 1767 self.__solve_wrapper.add_log_callback(self.log_callback) 1768 1769 if self.best_bound_callback is not None: 1770 self.__solve_wrapper.add_best_bound_callback(self.best_bound_callback) 1771 1772 self.__response = self.__solve_wrapper.solve(model.proto) 1773 1774 if solution_callback is not None: 1775 self.__solve_wrapper.clear_solution_callback(solution_callback) 1776 1777 with self.__lock: 1778 self.__solve_wrapper = None 1779 1780 return self.__response.status 1781 1782 def stop_search(self) -> None: 1783 """Stops the current search asynchronously.""" 1784 with self.__lock: 1785 if self.__solve_wrapper: 1786 self.__solve_wrapper.stop_search() 1787 1788 def value(self, expression: LinearExprT) -> int: 1789 """Returns the value of a linear expression after solve.""" 1790 return cmh.ResponseHelper.value(self._checked_response, expression) 1791 1792 def values(self, variables: _IndexOrSeries) -> pd.Series: 1793 """Returns the values of the input variables. 1794 1795 If `variables` is a `pd.Index`, then the output will be indexed by the 1796 variables. If `variables` is a `pd.Series` indexed by the underlying 1797 dimensions, then the output will be indexed by the same underlying 1798 dimensions. 1799 1800 Args: 1801 variables (Union[pd.Index, pd.Series]): The set of variables from which to 1802 get the values. 1803 1804 Returns: 1805 pd.Series: The values of all variables in the set. 1806 1807 Raises: 1808 RuntimeError: if solve() has not been called. 1809 """ 1810 response: cmh.CpSolverResponse = self._checked_response 1811 return pd.Series( 1812 data=[cmh.ResponseHelper.value(response, var) for var in variables], 1813 index=_get_index(variables), 1814 ) 1815 1816 def float_value(self, expression: LinearExprT) -> float: 1817 """Returns the value of a linear expression after solve.""" 1818 return cmh.ResponseHelper.float_value(self._checked_response, expression) 1819 1820 def float_values(self, expressions: _IndexOrSeries) -> pd.Series: 1821 """Returns the float values of the input linear expressions. 1822 1823 If `expressions` is a `pd.Index`, then the output will be indexed by the 1824 variables. If `variables` is a `pd.Series` indexed by the underlying 1825 dimensions, then the output will be indexed by the same underlying 1826 dimensions. 1827 1828 Args: 1829 expressions (Union[pd.Index, pd.Series]): The set of expressions from 1830 which to get the values. 1831 1832 Returns: 1833 pd.Series: The values of all variables in the set. 1834 1835 Raises: 1836 RuntimeError: if solve() has not been called. 1837 """ 1838 response: cmh.CpSolverResponse = self._checked_response 1839 return pd.Series( 1840 data=[ 1841 cmh.ResponseHelper.float_value(response, expr) for expr in expressions 1842 ], 1843 index=_get_index(expressions), 1844 ) 1845 1846 def boolean_value(self, literal: LiteralT) -> bool: 1847 """Returns the boolean value of a literal after solve.""" 1848 return cmh.ResponseHelper.boolean_value(self._checked_response, literal) 1849 1850 def boolean_values(self, variables: _IndexOrSeries) -> pd.Series: 1851 """Returns the values of the input variables. 1852 1853 If `variables` is a `pd.Index`, then the output will be indexed by the 1854 variables. If `variables` is a `pd.Series` indexed by the underlying 1855 dimensions, then the output will be indexed by the same underlying 1856 dimensions. 1857 1858 Args: 1859 variables (Union[pd.Index, pd.Series]): The set of variables from which to 1860 get the values. 1861 1862 Returns: 1863 pd.Series: The values of all variables in the set. 1864 1865 Raises: 1866 RuntimeError: if solve() has not been called. 1867 """ 1868 response: cmh.CpSolverResponse = self._checked_response 1869 return pd.Series( 1870 data=[ 1871 cmh.ResponseHelper.boolean_value(response, literal) 1872 for literal in variables 1873 ], 1874 index=_get_index(variables), 1875 ) 1876 1877 @property 1878 def objective_value(self) -> float: 1879 """Returns the value of the objective after solve.""" 1880 return self._checked_response.objective_value 1881 1882 @property 1883 def best_objective_bound(self) -> float: 1884 """Returns the best lower (upper) bound found when min(max)imizing.""" 1885 return self._checked_response.best_objective_bound 1886 1887 @property 1888 def num_booleans(self) -> int: 1889 """Returns the number of boolean variables managed by the SAT solver.""" 1890 return self._checked_response.num_booleans 1891 1892 @property 1893 def num_conflicts(self) -> int: 1894 """Returns the number of conflicts since the creation of the solver.""" 1895 return self._checked_response.num_conflicts 1896 1897 @property 1898 def num_branches(self) -> int: 1899 """Returns the number of search branches explored by the solver.""" 1900 return self._checked_response.num_branches 1901 1902 @property 1903 def num_binary_propagations(self) -> int: 1904 """Returns the number of Boolean propagations done by the solver.""" 1905 return self._checked_response.num_binary_propagations 1906 1907 @property 1908 def num_integer_propagations(self) -> int: 1909 """Returns the number of integer propagations done by the solver.""" 1910 return self._checked_response.num_integer_propagations 1911 1912 @property 1913 def deterministic_time(self) -> float: 1914 """Returns the deterministic time in seconds since the creation of the solver.""" 1915 return self._checked_response.deterministic_time 1916 1917 @property 1918 def wall_time(self) -> float: 1919 """Returns the wall time in seconds since the creation of the solver.""" 1920 return self._checked_response.wall_time 1921 1922 @property 1923 def user_time(self) -> float: 1924 """Returns the user time in seconds since the creation of the solver.""" 1925 return self._checked_response.user_time 1926 1927 @property 1928 def solve_log(self) -> str: 1929 """Returns the solve log. 1930 1931 To enable this, the parameter log_to_response must be set to True. 1932 """ 1933 return self._checked_response.solve_log 1934 1935 @property 1936 def solve_info(self) -> str: 1937 """Returns the information about the solve.""" 1938 return self._checked_response.solve_info 1939 1940 @property 1941 def response_proto(self) -> cmh.CpSolverResponse: 1942 """Returns the response object.""" 1943 return self._checked_response 1944 1945 def response_stats(self) -> str: 1946 """Returns some statistics on the solution found as a string.""" 1947 return cmh.CpSatHelper.solver_response_stats(self._checked_response) 1948 1949 def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]: 1950 """Returns the indices of the infeasible assumptions.""" 1951 return cmh.ResponseHelper.sufficient_assumptions_for_infeasibility( 1952 self._checked_response 1953 ) 1954 1955 def status_name(self, status: Optional[Any] = None) -> str: 1956 """Returns the name of the status returned by solve().""" 1957 if status is None: 1958 status = self._checked_response.status() 1959 return status.name 1960 1961 def solution_info(self) -> str: 1962 """Returns some information on the solve process. 1963 1964 Returns some information on how the solution was found, or the reason 1965 why the model or the parameters are invalid. 1966 1967 Raises: 1968 RuntimeError: if solve() has not been called. 1969 """ 1970 return self._checked_response.solution_info 1971 1972 @property 1973 def _checked_response(self) -> cmh.CpSolverResponse: 1974 """Checks solve() has been called, and returns a response wrapper.""" 1975 if self.__response is None: 1976 raise RuntimeError("solve() has not been called.") 1977 return self.__response 1978 1979 # Compatibility with pre PEP8 1980 # pylint: disable=invalid-name 1981 1982 @deprecated("Use best_objective_bound property instead.") 1983 def BestObjectiveBound(self) -> float: 1984 return self.best_objective_bound 1985 1986 @deprecated("Use boolean_value() method instead.") 1987 def BooleanValue(self, lit: LiteralT) -> bool: 1988 return self.boolean_value(lit) 1989 1990 @deprecated("Use boolean_values() method instead.") 1991 def BooleanValues(self, variables: _IndexOrSeries) -> pd.Series: 1992 return self.boolean_values(variables) 1993 1994 @deprecated("Use num_booleans property instead.") 1995 def NumBooleans(self) -> int: 1996 return self.num_booleans 1997 1998 @deprecated("Use num_conflicts property instead.") 1999 def NumConflicts(self) -> int: 2000 return self.num_conflicts 2001 2002 @deprecated("Use num_branches property instead.") 2003 def NumBranches(self) -> int: 2004 return self.num_branches 2005 2006 @deprecated("Use objective_value property instead.") 2007 def ObjectiveValue(self) -> float: 2008 return self.objective_value 2009 2010 @deprecated("Use response_proto property instead.") 2011 def ResponseProto(self) -> cmh.CpSolverResponse: 2012 return self.response_proto 2013 2014 @deprecated("Use response_stats() method instead.") 2015 def ResponseStats(self) -> str: 2016 return self.response_stats() 2017 2018 @deprecated("Use solve() method instead.") 2019 def Solve( 2020 self, model: CpModel, callback: "CpSolverSolutionCallback" = None 2021 ) -> cmh.CpSolverStatus: 2022 return self.solve(model, callback) 2023 2024 @deprecated("Use solution_info() method instead.") 2025 def SolutionInfo(self) -> str: 2026 return self.solution_info() 2027 2028 @deprecated("Use status_name() method instead.") 2029 def StatusName(self, status: Optional[Any] = None) -> str: 2030 return self.status_name(status) 2031 2032 @deprecated("Use stop_search() method instead.") 2033 def StopSearch(self) -> None: 2034 self.stop_search() 2035 2036 @deprecated("Use sufficient_assumptions_for_infeasibility() method instead.") 2037 def SufficientAssumptionsForInfeasibility(self) -> Sequence[int]: 2038 return self.sufficient_assumptions_for_infeasibility() 2039 2040 @deprecated("Use user_time property instead.") 2041 def UserTime(self) -> float: 2042 return self.user_time 2043 2044 @deprecated("Use value() method instead.") 2045 def Value(self, expression: LinearExprT) -> int: 2046 return self.value(expression) 2047 2048 @deprecated("Use values() method instead.") 2049 def Values(self, expressions: _IndexOrSeries) -> pd.Series: 2050 return self.values(expressions) 2051 2052 @deprecated("Use wall_time property instead.") 2053 def WallTime(self) -> float: 2054 return self.wall_time 2055 2056 @deprecated("Use solve() with enumerate_all_solutions = True.") 2057 def SearchForAllSolutions( 2058 self, model: CpModel, callback: "CpSolverSolutionCallback" 2059 ) -> cmh.CpSolverStatus: 2060 """Search for all solutions of a satisfiability problem. 2061 2062 This method searches for all feasible solutions of a given model. 2063 Then it feeds the solution to the callback. 2064 2065 Note that the model cannot contain an objective. 2066 2067 Args: 2068 model: The model to solve. 2069 callback: The callback that will be called at each solution. 2070 2071 Returns: 2072 The status of the solve: 2073 2074 * *FEASIBLE* if some solutions have been found 2075 * *INFEASIBLE* if the solver has proved there are no solution 2076 * *OPTIMAL* if all solutions have been found 2077 """ 2078 if model.has_objective(): 2079 raise TypeError( 2080 "Search for all solutions is only defined on satisfiability problems" 2081 ) 2082 # Store old parameter. 2083 enumerate_all = self.parameters.enumerate_all_solutions 2084 self.parameters.enumerate_all_solutions = True 2085 2086 status: cmh.CpSolverStatus = self.solve(model, callback) 2087 2088 # Restore parameter. 2089 self.parameters.enumerate_all_solutions = enumerate_all 2090 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.
1753 def solve( 1754 self, 1755 model: CpModel, 1756 solution_callback: Optional["CpSolverSolutionCallback"] = None, 1757 ) -> cmh.CpSolverStatus: 1758 """Solves a problem and passes each solution to the callback if not null.""" 1759 with self.__lock: 1760 self.__solve_wrapper = cmh.SolveWrapper() 1761 1762 self.__solve_wrapper.set_parameters(self.parameters) 1763 if solution_callback is not None: 1764 self.__solve_wrapper.add_solution_callback(solution_callback) 1765 1766 if self.log_callback is not None: 1767 self.__solve_wrapper.add_log_callback(self.log_callback) 1768 1769 if self.best_bound_callback is not None: 1770 self.__solve_wrapper.add_best_bound_callback(self.best_bound_callback) 1771 1772 self.__response = self.__solve_wrapper.solve(model.proto) 1773 1774 if solution_callback is not None: 1775 self.__solve_wrapper.clear_solution_callback(solution_callback) 1776 1777 with self.__lock: 1778 self.__solve_wrapper = None 1779 1780 return self.__response.status
Solves a problem and passes each solution to the callback if not null.
1782 def stop_search(self) -> None: 1783 """Stops the current search asynchronously.""" 1784 with self.__lock: 1785 if self.__solve_wrapper: 1786 self.__solve_wrapper.stop_search()
Stops the current search asynchronously.
1788 def value(self, expression: LinearExprT) -> int: 1789 """Returns the value of a linear expression after solve.""" 1790 return cmh.ResponseHelper.value(self._checked_response, expression)
Returns the value of a linear expression after solve.
1792 def values(self, variables: _IndexOrSeries) -> pd.Series: 1793 """Returns the values of the input variables. 1794 1795 If `variables` is a `pd.Index`, then the output will be indexed by the 1796 variables. If `variables` is a `pd.Series` indexed by the underlying 1797 dimensions, then the output will be indexed by the same underlying 1798 dimensions. 1799 1800 Args: 1801 variables (Union[pd.Index, pd.Series]): The set of variables from which to 1802 get the values. 1803 1804 Returns: 1805 pd.Series: The values of all variables in the set. 1806 1807 Raises: 1808 RuntimeError: if solve() has not been called. 1809 """ 1810 response: cmh.CpSolverResponse = self._checked_response 1811 return pd.Series( 1812 data=[cmh.ResponseHelper.value(response, var) for var in variables], 1813 index=_get_index(variables), 1814 )
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.
1816 def float_value(self, expression: LinearExprT) -> float: 1817 """Returns the value of a linear expression after solve.""" 1818 return cmh.ResponseHelper.float_value(self._checked_response, expression)
Returns the value of a linear expression after solve.
1820 def float_values(self, expressions: _IndexOrSeries) -> pd.Series: 1821 """Returns the float values of the input linear expressions. 1822 1823 If `expressions` is a `pd.Index`, then the output will be indexed by the 1824 variables. If `variables` is a `pd.Series` indexed by the underlying 1825 dimensions, then the output will be indexed by the same underlying 1826 dimensions. 1827 1828 Args: 1829 expressions (Union[pd.Index, pd.Series]): The set of expressions from 1830 which to get the values. 1831 1832 Returns: 1833 pd.Series: The values of all variables in the set. 1834 1835 Raises: 1836 RuntimeError: if solve() has not been called. 1837 """ 1838 response: cmh.CpSolverResponse = self._checked_response 1839 return pd.Series( 1840 data=[ 1841 cmh.ResponseHelper.float_value(response, expr) for expr in expressions 1842 ], 1843 index=_get_index(expressions), 1844 )
Returns the float values of the input linear expressions.
If expressions 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:
- expressions (Union[pd.Index, pd.Series]): The set of expressions 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.
1846 def boolean_value(self, literal: LiteralT) -> bool: 1847 """Returns the boolean value of a literal after solve.""" 1848 return cmh.ResponseHelper.boolean_value(self._checked_response, literal)
Returns the boolean value of a literal after solve.
1850 def boolean_values(self, variables: _IndexOrSeries) -> pd.Series: 1851 """Returns the values of the input variables. 1852 1853 If `variables` is a `pd.Index`, then the output will be indexed by the 1854 variables. If `variables` is a `pd.Series` indexed by the underlying 1855 dimensions, then the output will be indexed by the same underlying 1856 dimensions. 1857 1858 Args: 1859 variables (Union[pd.Index, pd.Series]): The set of variables from which to 1860 get the values. 1861 1862 Returns: 1863 pd.Series: The values of all variables in the set. 1864 1865 Raises: 1866 RuntimeError: if solve() has not been called. 1867 """ 1868 response: cmh.CpSolverResponse = self._checked_response 1869 return pd.Series( 1870 data=[ 1871 cmh.ResponseHelper.boolean_value(response, literal) 1872 for literal in variables 1873 ], 1874 index=_get_index(variables), 1875 )
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.
1877 @property 1878 def objective_value(self) -> float: 1879 """Returns the value of the objective after solve.""" 1880 return self._checked_response.objective_value
Returns the value of the objective after solve.
1882 @property 1883 def best_objective_bound(self) -> float: 1884 """Returns the best lower (upper) bound found when min(max)imizing.""" 1885 return self._checked_response.best_objective_bound
Returns the best lower (upper) bound found when min(max)imizing.
1887 @property 1888 def num_booleans(self) -> int: 1889 """Returns the number of boolean variables managed by the SAT solver.""" 1890 return self._checked_response.num_booleans
Returns the number of boolean variables managed by the SAT solver.
1892 @property 1893 def num_conflicts(self) -> int: 1894 """Returns the number of conflicts since the creation of the solver.""" 1895 return self._checked_response.num_conflicts
Returns the number of conflicts since the creation of the solver.
1897 @property 1898 def num_branches(self) -> int: 1899 """Returns the number of search branches explored by the solver.""" 1900 return self._checked_response.num_branches
Returns the number of search branches explored by the solver.
1902 @property 1903 def num_binary_propagations(self) -> int: 1904 """Returns the number of Boolean propagations done by the solver.""" 1905 return self._checked_response.num_binary_propagations
Returns the number of Boolean propagations done by the solver.
1907 @property 1908 def num_integer_propagations(self) -> int: 1909 """Returns the number of integer propagations done by the solver.""" 1910 return self._checked_response.num_integer_propagations
Returns the number of integer propagations done by the solver.
1912 @property 1913 def deterministic_time(self) -> float: 1914 """Returns the deterministic time in seconds since the creation of the solver.""" 1915 return self._checked_response.deterministic_time
Returns the deterministic time in seconds since the creation of the solver.
1917 @property 1918 def wall_time(self) -> float: 1919 """Returns the wall time in seconds since the creation of the solver.""" 1920 return self._checked_response.wall_time
Returns the wall time in seconds since the creation of the solver.
1922 @property 1923 def user_time(self) -> float: 1924 """Returns the user time in seconds since the creation of the solver.""" 1925 return self._checked_response.user_time
Returns the user time in seconds since the creation of the solver.
1927 @property 1928 def solve_log(self) -> str: 1929 """Returns the solve log. 1930 1931 To enable this, the parameter log_to_response must be set to True. 1932 """ 1933 return self._checked_response.solve_log
Returns the solve log.
To enable this, the parameter log_to_response must be set to True.
1935 @property 1936 def solve_info(self) -> str: 1937 """Returns the information about the solve.""" 1938 return self._checked_response.solve_info
Returns the information about the solve.
1940 @property 1941 def response_proto(self) -> cmh.CpSolverResponse: 1942 """Returns the response object.""" 1943 return self._checked_response
Returns the response object.
1945 def response_stats(self) -> str: 1946 """Returns some statistics on the solution found as a string.""" 1947 return cmh.CpSatHelper.solver_response_stats(self._checked_response)
Returns some statistics on the solution found as a string.
1949 def sufficient_assumptions_for_infeasibility(self) -> Sequence[int]: 1950 """Returns the indices of the infeasible assumptions.""" 1951 return cmh.ResponseHelper.sufficient_assumptions_for_infeasibility( 1952 self._checked_response 1953 )
Returns the indices of the infeasible assumptions.
1955 def status_name(self, status: Optional[Any] = None) -> str: 1956 """Returns the name of the status returned by solve().""" 1957 if status is None: 1958 status = self._checked_response.status() 1959 return status.name
Returns the name of the status returned by solve().
1961 def solution_info(self) -> str: 1962 """Returns some information on the solve process. 1963 1964 Returns some information on how the solution was found, or the reason 1965 why the model or the parameters are invalid. 1966 1967 Raises: 1968 RuntimeError: if solve() has not been called. 1969 """ 1970 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.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
196 def deprecated_func(*args, **kwargs): 197 if enable_warnings: 198 warnings.warn( 199 f"{func.__name__} is a deprecated function. {message}", 200 category=DeprecationWarning, 201 stacklevel=2, 202 ) 203 warnings.simplefilter("default", DeprecationWarning) 204 return func(*args, **kwargs)
The type of the None singleton.
2096class CpSolverSolutionCallback(cmh.SolutionCallback): 2097 """Solution callback. 2098 2099 This class implements a callback that will be called at each new solution 2100 found during search. 2101 2102 The method on_solution_callback() will be called by the solver, and must be 2103 implemented. The current solution can be queried using the boolean_value() 2104 and value() methods. 2105 2106 These methods returns the same information as their counterpart in the 2107 `CpSolver` class. 2108 """ 2109 2110 def __init__(self) -> None: 2111 cmh.SolutionCallback.__init__(self) 2112 2113 # pylint: disable=invalid-name 2114 def OnSolutionCallback(self) -> None: 2115 """Proxy for the same method in snake case.""" 2116 self.on_solution_callback() 2117 2118 # pylint: enable=invalid-name 2119 2120 def boolean_value(self, lit: LiteralT) -> bool: 2121 """Returns the boolean value of a boolean literal. 2122 2123 Args: 2124 lit: A boolean variable or its negation. 2125 2126 Returns: 2127 The Boolean value of the literal in the solution. 2128 2129 Raises: 2130 RuntimeError: if `lit` is not a boolean variable or its negation. 2131 """ 2132 if not self.has_response(): 2133 raise RuntimeError("solve() has not been called.") 2134 return self.BooleanValue(lit) 2135 2136 def value(self, expression: LinearExprT) -> int: 2137 """Evaluates an linear expression in the current solution. 2138 2139 Args: 2140 expression: a linear expression of the model. 2141 2142 Returns: 2143 An integer value equal to the evaluation of the linear expression 2144 against the current solution. 2145 2146 Raises: 2147 RuntimeError: if 'expression' is not a LinearExpr. 2148 """ 2149 if not self.has_response(): 2150 raise RuntimeError("solve() has not been called.") 2151 return self.Value(expression) 2152 2153 def float_value(self, expression: LinearExprT) -> float: 2154 """Evaluates an linear expression in the current solution. 2155 2156 Args: 2157 expression: a linear expression of the model. 2158 2159 Returns: 2160 An integer value equal to the evaluation of the linear expression 2161 against the current solution. 2162 2163 Raises: 2164 RuntimeError: if 'expression' is not a LinearExpr. 2165 """ 2166 if not self.has_response(): 2167 raise RuntimeError("solve() has not been called.") 2168 return self.FloatValue(expression) 2169 2170 def has_response(self) -> bool: 2171 return self.HasResponse() 2172 2173 def stop_search(self) -> None: 2174 """Stops the current search asynchronously.""" 2175 if not self.has_response(): 2176 raise RuntimeError("solve() has not been called.") 2177 self.StopSearch() 2178 2179 @property 2180 def objective_value(self) -> float: 2181 """Returns the value of the objective after solve.""" 2182 if not self.has_response(): 2183 raise RuntimeError("solve() has not been called.") 2184 return self.ObjectiveValue() 2185 2186 @property 2187 def best_objective_bound(self) -> float: 2188 """Returns the best lower (upper) bound found when min(max)imizing.""" 2189 if not self.has_response(): 2190 raise RuntimeError("solve() has not been called.") 2191 return self.BestObjectiveBound() 2192 2193 @property 2194 def num_booleans(self) -> int: 2195 """Returns the number of boolean variables managed by the SAT solver.""" 2196 if not self.has_response(): 2197 raise RuntimeError("solve() has not been called.") 2198 return self.NumBooleans() 2199 2200 @property 2201 def num_conflicts(self) -> int: 2202 """Returns the number of conflicts since the creation of the solver.""" 2203 if not self.has_response(): 2204 raise RuntimeError("solve() has not been called.") 2205 return self.NumConflicts() 2206 2207 @property 2208 def num_branches(self) -> int: 2209 """Returns the number of search branches explored by the solver.""" 2210 if not self.has_response(): 2211 raise RuntimeError("solve() has not been called.") 2212 return self.NumBranches() 2213 2214 @property 2215 def num_integer_propagations(self) -> int: 2216 """Returns the number of integer propagations done by the solver.""" 2217 if not self.has_response(): 2218 raise RuntimeError("solve() has not been called.") 2219 return self.NumIntegerPropagations() 2220 2221 @property 2222 def num_binary_propagations(self) -> int: 2223 """Returns the number of Boolean propagations done by the solver.""" 2224 if not self.has_response(): 2225 raise RuntimeError("solve() has not been called.") 2226 return self.NumBinaryPropagations() 2227 2228 @property 2229 def deterministic_time(self) -> float: 2230 """Returns the determistic time in seconds since the creation of the solver.""" 2231 if not self.has_response(): 2232 raise RuntimeError("solve() has not been called.") 2233 return self.DeterministicTime() 2234 2235 @property 2236 def wall_time(self) -> float: 2237 """Returns the wall time in seconds since the creation of the solver.""" 2238 if not self.has_response(): 2239 raise RuntimeError("solve() has not been called.") 2240 return self.WallTime() 2241 2242 @property 2243 def user_time(self) -> float: 2244 """Returns the user time in seconds since the creation of the solver.""" 2245 if not self.has_response(): 2246 raise RuntimeError("solve() has not been called.") 2247 return self.UserTime() 2248 2249 @property 2250 def response_proto(self) -> cmh.CpSolverResponse: 2251 """Returns the response object.""" 2252 if not self.has_response(): 2253 raise RuntimeError("solve() has not been called.") 2254 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.
__init__(self: ortools.sat.python.cp_model_helper.SolutionCallback) -> None
2114 def OnSolutionCallback(self) -> None: 2115 """Proxy for the same method in snake case.""" 2116 self.on_solution_callback()
Proxy for the same method in snake case.
2120 def boolean_value(self, lit: LiteralT) -> bool: 2121 """Returns the boolean value of a boolean literal. 2122 2123 Args: 2124 lit: A boolean variable or its negation. 2125 2126 Returns: 2127 The Boolean value of the literal in the solution. 2128 2129 Raises: 2130 RuntimeError: if `lit` is not a boolean variable or its negation. 2131 """ 2132 if not self.has_response(): 2133 raise RuntimeError("solve() has not been called.") 2134 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
litis not a boolean variable or its negation.
2136 def value(self, expression: LinearExprT) -> int: 2137 """Evaluates an linear expression in the current solution. 2138 2139 Args: 2140 expression: a linear expression of the model. 2141 2142 Returns: 2143 An integer value equal to the evaluation of the linear expression 2144 against the current solution. 2145 2146 Raises: 2147 RuntimeError: if 'expression' is not a LinearExpr. 2148 """ 2149 if not self.has_response(): 2150 raise RuntimeError("solve() has not been called.") 2151 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.
2153 def float_value(self, expression: LinearExprT) -> float: 2154 """Evaluates an linear expression in the current solution. 2155 2156 Args: 2157 expression: a linear expression of the model. 2158 2159 Returns: 2160 An integer value equal to the evaluation of the linear expression 2161 against the current solution. 2162 2163 Raises: 2164 RuntimeError: if 'expression' is not a LinearExpr. 2165 """ 2166 if not self.has_response(): 2167 raise RuntimeError("solve() has not been called.") 2168 return self.FloatValue(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.
2173 def stop_search(self) -> None: 2174 """Stops the current search asynchronously.""" 2175 if not self.has_response(): 2176 raise RuntimeError("solve() has not been called.") 2177 self.StopSearch()
Stops the current search asynchronously.
2179 @property 2180 def objective_value(self) -> float: 2181 """Returns the value of the objective after solve.""" 2182 if not self.has_response(): 2183 raise RuntimeError("solve() has not been called.") 2184 return self.ObjectiveValue()
Returns the value of the objective after solve.
2186 @property 2187 def best_objective_bound(self) -> float: 2188 """Returns the best lower (upper) bound found when min(max)imizing.""" 2189 if not self.has_response(): 2190 raise RuntimeError("solve() has not been called.") 2191 return self.BestObjectiveBound()
Returns the best lower (upper) bound found when min(max)imizing.
2193 @property 2194 def num_booleans(self) -> int: 2195 """Returns the number of boolean variables managed by the SAT solver.""" 2196 if not self.has_response(): 2197 raise RuntimeError("solve() has not been called.") 2198 return self.NumBooleans()
Returns the number of boolean variables managed by the SAT solver.
2200 @property 2201 def num_conflicts(self) -> int: 2202 """Returns the number of conflicts since the creation of the solver.""" 2203 if not self.has_response(): 2204 raise RuntimeError("solve() has not been called.") 2205 return self.NumConflicts()
Returns the number of conflicts since the creation of the solver.
2207 @property 2208 def num_branches(self) -> int: 2209 """Returns the number of search branches explored by the solver.""" 2210 if not self.has_response(): 2211 raise RuntimeError("solve() has not been called.") 2212 return self.NumBranches()
Returns the number of search branches explored by the solver.
2214 @property 2215 def num_integer_propagations(self) -> int: 2216 """Returns the number of integer propagations done by the solver.""" 2217 if not self.has_response(): 2218 raise RuntimeError("solve() has not been called.") 2219 return self.NumIntegerPropagations()
Returns the number of integer propagations done by the solver.
2221 @property 2222 def num_binary_propagations(self) -> int: 2223 """Returns the number of Boolean propagations done by the solver.""" 2224 if not self.has_response(): 2225 raise RuntimeError("solve() has not been called.") 2226 return self.NumBinaryPropagations()
Returns the number of Boolean propagations done by the solver.
2228 @property 2229 def deterministic_time(self) -> float: 2230 """Returns the determistic time in seconds since the creation of the solver.""" 2231 if not self.has_response(): 2232 raise RuntimeError("solve() has not been called.") 2233 return self.DeterministicTime()
Returns the determistic time in seconds since the creation of the solver.
2235 @property 2236 def wall_time(self) -> float: 2237 """Returns the wall time in seconds since the creation of the solver.""" 2238 if not self.has_response(): 2239 raise RuntimeError("solve() has not been called.") 2240 return self.WallTime()
Returns the wall time in seconds since the creation of the solver.
2242 @property 2243 def user_time(self) -> float: 2244 """Returns the user time in seconds since the creation of the solver.""" 2245 if not self.has_response(): 2246 raise RuntimeError("solve() has not been called.") 2247 return self.UserTime()
Returns the user time in seconds since the creation of the solver.
2249 @property 2250 def response_proto(self) -> cmh.CpSolverResponse: 2251 """Returns the response object.""" 2252 if not self.has_response(): 2253 raise RuntimeError("solve() has not been called.") 2254 return self.Response()
Returns the response object.
Inherited Members
- ortools.sat.python.cp_model_helper.SolutionCallback
- BestObjectiveBound
- DeterministicTime
- HasResponse
- NumBinaryPropagations
- NumBooleans
- NumBranches
- NumConflicts
- NumIntegerPropagations
- ObjectiveValue
- Response
- SolutionBooleanValue
- SolutionIntegerValue
- StopSearch
- UserTime
- WallTime
- Value
- FloatValue
- BooleanValue
2257class ObjectiveSolutionPrinter(CpSolverSolutionCallback): 2258 """Display the objective value and time of intermediate solutions.""" 2259 2260 def __init__(self) -> None: 2261 CpSolverSolutionCallback.__init__(self) 2262 self.__solution_count = 0 2263 self.__start_time = time.time() 2264 2265 def on_solution_callback(self) -> None: 2266 """Called on each new solution.""" 2267 current_time = time.time() 2268 obj = self.objective_value 2269 print( 2270 f"Solution {self.__solution_count}, time =" 2271 f" {current_time - self.__start_time:0.2f} s, objective = {obj}", 2272 flush=True, 2273 ) 2274 self.__solution_count += 1 2275 2276 def solution_count(self) -> int: 2277 """Returns the number of solutions found.""" 2278 return self.__solution_count
Display the objective value and time of intermediate solutions.
2260 def __init__(self) -> None: 2261 CpSolverSolutionCallback.__init__(self) 2262 self.__solution_count = 0 2263 self.__start_time = time.time()
__init__(self: ortools.sat.python.cp_model_helper.SolutionCallback) -> None
2265 def on_solution_callback(self) -> None: 2266 """Called on each new solution.""" 2267 current_time = time.time() 2268 obj = self.objective_value 2269 print( 2270 f"Solution {self.__solution_count}, time =" 2271 f" {current_time - self.__start_time:0.2f} s, objective = {obj}", 2272 flush=True, 2273 ) 2274 self.__solution_count += 1
Called on each new solution.
2276 def solution_count(self) -> int: 2277 """Returns the number of solutions found.""" 2278 return self.__solution_count
Returns the number of solutions found.
Inherited Members
- CpSolverSolutionCallback
- OnSolutionCallback
- boolean_value
- value
- float_value
- has_response
- stop_search
- objective_value
- best_objective_bound
- num_booleans
- num_conflicts
- num_branches
- num_integer_propagations
- num_binary_propagations
- deterministic_time
- wall_time
- user_time
- response_proto
- ortools.sat.python.cp_model_helper.SolutionCallback
- BestObjectiveBound
- DeterministicTime
- HasResponse
- NumBinaryPropagations
- NumBooleans
- NumBranches
- NumConflicts
- NumIntegerPropagations
- ObjectiveValue
- Response
- SolutionBooleanValue
- SolutionIntegerValue
- StopSearch
- UserTime
- WallTime
- Value
- FloatValue
- BooleanValue
2281class VarArrayAndObjectiveSolutionPrinter(CpSolverSolutionCallback): 2282 """Print intermediate solutions (objective, variable values, time).""" 2283 2284 def __init__(self, variables: Sequence[IntVar]) -> None: 2285 CpSolverSolutionCallback.__init__(self) 2286 self.__variables: Sequence[IntVar] = variables 2287 self.__solution_count: int = 0 2288 self.__start_time: float = time.time() 2289 2290 def on_solution_callback(self) -> None: 2291 """Called on each new solution.""" 2292 current_time = time.time() 2293 obj = self.objective_value 2294 print( 2295 f"Solution {self.__solution_count}, time =" 2296 f" {current_time - self.__start_time:0.2f} s, objective = {obj}" 2297 ) 2298 for v in self.__variables: 2299 print(f" {v} = {self.value(v)}", end=" ") 2300 print(flush=True) 2301 self.__solution_count += 1 2302 2303 @property 2304 def solution_count(self) -> int: 2305 """Returns the number of solutions found.""" 2306 return self.__solution_count
Print intermediate solutions (objective, variable values, time).
2284 def __init__(self, variables: Sequence[IntVar]) -> None: 2285 CpSolverSolutionCallback.__init__(self) 2286 self.__variables: Sequence[IntVar] = variables 2287 self.__solution_count: int = 0 2288 self.__start_time: float = time.time()
__init__(self: ortools.sat.python.cp_model_helper.SolutionCallback) -> None
2290 def on_solution_callback(self) -> None: 2291 """Called on each new solution.""" 2292 current_time = time.time() 2293 obj = self.objective_value 2294 print( 2295 f"Solution {self.__solution_count}, time =" 2296 f" {current_time - self.__start_time:0.2f} s, objective = {obj}" 2297 ) 2298 for v in self.__variables: 2299 print(f" {v} = {self.value(v)}", end=" ") 2300 print(flush=True) 2301 self.__solution_count += 1
Called on each new solution.
2303 @property 2304 def solution_count(self) -> int: 2305 """Returns the number of solutions found.""" 2306 return self.__solution_count
Returns the number of solutions found.
Inherited Members
- CpSolverSolutionCallback
- OnSolutionCallback
- boolean_value
- value
- float_value
- has_response
- stop_search
- objective_value
- best_objective_bound
- num_booleans
- num_conflicts
- num_branches
- num_integer_propagations
- num_binary_propagations
- deterministic_time
- wall_time
- user_time
- response_proto
- ortools.sat.python.cp_model_helper.SolutionCallback
- BestObjectiveBound
- DeterministicTime
- HasResponse
- NumBinaryPropagations
- NumBooleans
- NumBranches
- NumConflicts
- NumIntegerPropagations
- ObjectiveValue
- Response
- SolutionBooleanValue
- SolutionIntegerValue
- StopSearch
- UserTime
- WallTime
- Value
- FloatValue
- BooleanValue
2309class VarArraySolutionPrinter(CpSolverSolutionCallback): 2310 """Print intermediate solutions (variable values, time).""" 2311 2312 def __init__(self, variables: Sequence[IntVar]) -> None: 2313 CpSolverSolutionCallback.__init__(self) 2314 self.__variables: Sequence[IntVar] = variables 2315 self.__solution_count: int = 0 2316 self.__start_time: float = time.time() 2317 2318 def on_solution_callback(self) -> None: 2319 """Called on each new solution.""" 2320 current_time = time.time() 2321 print( 2322 f"Solution {self.__solution_count}, time =" 2323 f" {current_time - self.__start_time:0.2f} s" 2324 ) 2325 for v in self.__variables: 2326 print(f" {v} = {self.value(v)}", end=" ") 2327 print(flush=True) 2328 self.__solution_count += 1 2329 2330 @property 2331 def solution_count(self) -> int: 2332 """Returns the number of solutions found.""" 2333 return self.__solution_count
Print intermediate solutions (variable values, time).
2312 def __init__(self, variables: Sequence[IntVar]) -> None: 2313 CpSolverSolutionCallback.__init__(self) 2314 self.__variables: Sequence[IntVar] = variables 2315 self.__solution_count: int = 0 2316 self.__start_time: float = time.time()
__init__(self: ortools.sat.python.cp_model_helper.SolutionCallback) -> None
2318 def on_solution_callback(self) -> None: 2319 """Called on each new solution.""" 2320 current_time = time.time() 2321 print( 2322 f"Solution {self.__solution_count}, time =" 2323 f" {current_time - self.__start_time:0.2f} s" 2324 ) 2325 for v in self.__variables: 2326 print(f" {v} = {self.value(v)}", end=" ") 2327 print(flush=True) 2328 self.__solution_count += 1
Called on each new solution.
2330 @property 2331 def solution_count(self) -> int: 2332 """Returns the number of solutions found.""" 2333 return self.__solution_count
Returns the number of solutions found.
Inherited Members
- CpSolverSolutionCallback
- OnSolutionCallback
- boolean_value
- value
- float_value
- has_response
- stop_search
- objective_value
- best_objective_bound
- num_booleans
- num_conflicts
- num_branches
- num_integer_propagations
- num_binary_propagations
- deterministic_time
- wall_time
- user_time
- response_proto
- ortools.sat.python.cp_model_helper.SolutionCallback
- BestObjectiveBound
- DeterministicTime
- HasResponse
- NumBinaryPropagations
- NumBooleans
- NumBranches
- NumConflicts
- NumIntegerPropagations
- ObjectiveValue
- Response
- SolutionBooleanValue
- SolutionIntegerValue
- StopSearch
- UserTime
- WallTime
- Value
- FloatValue
- BooleanValue