ortools.linear_solver.python.model_builder

Methods for building and solving model_builder models.

The following two sections describe the main methods for building and solving those models.

  • Model: Methods for creating models, including variables and constraints.
  • Solver: Methods for solving a model and evaluating solutions.

Additional methods for solving Model models:

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

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

   1# Copyright 2010-2024 Google LLC
   2# Licensed under the Apache License, Version 2.0 (the "License");
   3# you may not use this file except in compliance with the License.
   4# You may obtain a copy of the License at
   5#
   6#     http://www.apache.org/licenses/LICENSE-2.0
   7#
   8# Unless required by applicable law or agreed to in writing, software
   9# distributed under the License is distributed on an "AS IS" BASIS,
  10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11# See the License for the specific language governing permissions and
  12# limitations under the License.
  13
  14"""Methods for building and solving model_builder models.
  15
  16The following two sections describe the main
  17methods for building and solving those models.
  18
  19* [`Model`](#model_builder.Model): Methods for creating
  20models, including variables and constraints.
  21* [`Solver`](#model_builder.Solver): Methods for solving
  22a model and evaluating solutions.
  23
  24Additional methods for solving Model models:
  25
  26* [`Constraint`](#model_builder.Constraint): A few utility methods for modifying
  27  constraints created by `Model`.
  28* [`LinearExpr`](#model_builder.LinearExpr): Methods for creating constraints
  29  and the objective from large arrays of coefficients.
  30
  31Other methods and functions listed are primarily used for developing OR-Tools,
  32rather than for solving specific optimization problems.
  33"""
  34
  35import abc
  36import dataclasses
  37import math
  38import numbers
  39import typing
  40from typing import Callable, List, Optional, Sequence, Tuple, Union, cast
  41
  42import numpy as np
  43from numpy import typing as npt
  44import pandas as pd
  45
  46from ortools.linear_solver import linear_solver_pb2
  47from ortools.linear_solver.python import model_builder_helper as mbh
  48from ortools.linear_solver.python import model_builder_numbers as mbn
  49
  50
  51# Custom types.
  52NumberT = Union[int, float, numbers.Real, np.number]
  53IntegerT = Union[int, numbers.Integral, np.integer]
  54LinearExprT = Union["LinearExpr", NumberT]
  55ConstraintT = Union["_BoundedLinearExpr", bool]
  56_IndexOrSeries = Union[pd.Index, pd.Series]
  57_VariableOrConstraint = Union["LinearConstraint", "Variable"]
  58
  59# Forward solve statuses.
  60SolveStatus = mbh.SolveStatus
  61
  62# pylint: disable=protected-access
  63
  64
  65class LinearExpr(metaclass=abc.ABCMeta):
  66    """Holds an linear expression.
  67
  68    A linear expression is built from constants and variables.
  69    For example, `x + 2.0 * (y - z + 1.0)`.
  70
  71    Linear expressions are used in Model models in constraints and in the
  72    objective:
  73
  74    * You can define linear constraints as in:
  75
  76    ```
  77    model.add(x + 2 * y <= 5.0)
  78    model.add(sum(array_of_vars) == 5.0)
  79    ```
  80
  81    * In Model, the objective is a linear expression:
  82
  83    ```
  84    model.minimize(x + 2.0 * y + z)
  85    ```
  86
  87    * For large arrays, using the LinearExpr class is faster that using the python
  88    `sum()` function. You can create constraints and the objective from lists of
  89    linear expressions or coefficients as follows:
  90
  91    ```
  92    model.minimize(model_builder.LinearExpr.sum(expressions))
  93    model.add(model_builder.LinearExpr.weighted_sum(expressions, coeffs) >= 0)
  94    ```
  95    """
  96
  97    @classmethod
  98    def sum(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
  99        cls, expressions: Sequence[LinearExprT], *, constant: NumberT = 0.0
 100    ) -> LinearExprT:
 101        """Creates `sum(expressions) + constant`.
 102
 103        It can perform simple simplifications and returns different objects,
 104        including the input.
 105
 106        Args:
 107          expressions: a sequence of linear expressions or constants.
 108          constant: a numerical constant.
 109
 110        Returns:
 111          a LinearExpr instance or a numerical constant.
 112        """
 113        checked_constant: np.double = mbn.assert_is_a_number(constant)
 114        if not expressions:
 115            return checked_constant
 116        if len(expressions) == 1 and mbn.is_zero(checked_constant):
 117            return expressions[0]
 118
 119        return LinearExpr.weighted_sum(
 120            expressions, np.ones(len(expressions)), constant=checked_constant
 121        )
 122
 123    @classmethod
 124    def weighted_sum(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
 125        cls,
 126        expressions: Sequence[LinearExprT],
 127        coefficients: Sequence[NumberT],
 128        *,
 129        constant: NumberT = 0.0,
 130    ) -> Union[NumberT, "_LinearExpression"]:
 131        """Creates `sum(expressions[i] * coefficients[i]) + constant`.
 132
 133        It can perform simple simplifications and returns different object,
 134        including the input.
 135
 136        Args:
 137          expressions: a sequence of linear expressions or constants.
 138          coefficients: a sequence of numerical constants.
 139          constant: a numerical constant.
 140
 141        Returns:
 142          a _LinearExpression instance or a numerical constant.
 143        """
 144        if len(expressions) != len(coefficients):
 145            raise ValueError(
 146                "LinearExpr.weighted_sum: expressions and coefficients have"
 147                " different lengths"
 148            )
 149        checked_constant: np.double = mbn.assert_is_a_number(constant)
 150        if not expressions:
 151            return checked_constant
 152        return _sum_as_flat_linear_expression(
 153            to_process=list(zip(expressions, coefficients)), offset=checked_constant
 154        )
 155
 156    @classmethod
 157    def term(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
 158        cls,
 159        expression: LinearExprT,
 160        coefficient: NumberT,
 161        *,
 162        constant: NumberT = 0.0,
 163    ) -> LinearExprT:
 164        """Creates `expression * coefficient + constant`.
 165
 166        It can perform simple simplifications and returns different object,
 167        including the input.
 168        Args:
 169          expression: a linear expression or a constant.
 170          coefficient: a numerical constant.
 171          constant: a numerical constant.
 172
 173        Returns:
 174          a LinearExpr instance or a numerical constant.
 175        """
 176        checked_coefficient: np.double = mbn.assert_is_a_number(coefficient)
 177        checked_constant: np.double = mbn.assert_is_a_number(constant)
 178
 179        if mbn.is_zero(checked_coefficient):
 180            return checked_constant
 181        if mbn.is_one(checked_coefficient) and mbn.is_zero(checked_constant):
 182            return expression
 183        if mbn.is_a_number(expression):
 184            return np.double(expression) * checked_coefficient + checked_constant
 185        if isinstance(expression, LinearExpr):
 186            return _as_flat_linear_expression(
 187                expression * checked_coefficient + checked_constant
 188            )
 189        raise TypeError(f"Unknown expression {expression!r} of type {type(expression)}")
 190
 191    def __hash__(self):
 192        return object.__hash__(self)
 193
 194    def __add__(self, arg: LinearExprT) -> "_Sum":
 195        return _Sum(self, arg)
 196
 197    def __radd__(self, arg: LinearExprT) -> "_Sum":
 198        return self.__add__(arg)
 199
 200    def __sub__(self, arg: LinearExprT) -> "_Sum":
 201        return _Sum(self, -arg)
 202
 203    def __rsub__(self, arg: LinearExprT) -> "_Sum":
 204        return _Sum(-self, arg)
 205
 206    def __mul__(self, arg: NumberT) -> "_Product":
 207        return _Product(self, arg)
 208
 209    def __rmul__(self, arg: NumberT) -> "_Product":
 210        return self.__mul__(arg)
 211
 212    def __truediv__(self, coeff: NumberT) -> "_Product":
 213        return self.__mul__(1.0 / coeff)
 214
 215    def __neg__(self) -> "_Product":
 216        return _Product(self, -1)
 217
 218    def __bool__(self):
 219        raise NotImplementedError(f"Cannot use a LinearExpr {self} as a Boolean value")
 220
 221    def __eq__(self, arg: LinearExprT) -> "BoundedLinearExpression":
 222        return BoundedLinearExpression(self - arg, 0, 0)
 223
 224    def __ge__(self, arg: LinearExprT) -> "BoundedLinearExpression":
 225        return BoundedLinearExpression(
 226            self - arg, 0, math.inf
 227        )  # pytype: disable=wrong-arg-types  # numpy-scalars
 228
 229    def __le__(self, arg: LinearExprT) -> "BoundedLinearExpression":
 230        return BoundedLinearExpression(
 231            self - arg, -math.inf, 0
 232        )  # pytype: disable=wrong-arg-types  # numpy-scalars
 233
 234
 235class Variable(LinearExpr):
 236    """A variable (continuous or integral).
 237
 238    A Variable is an object that can take on any integer value within defined
 239    ranges. Variables appear in constraint like:
 240
 241        x + y >= 5
 242
 243    Solving a model is equivalent to finding, for each variable, a single value
 244    from the set of initial values (called the initial domain), such that the
 245    model is feasible, or optimal if you provided an objective function.
 246    """
 247
 248    def __init__(
 249        self,
 250        helper: mbh.ModelBuilderHelper,
 251        lb: NumberT,
 252        ub: Optional[NumberT],
 253        is_integral: Optional[bool],
 254        name: Optional[str],
 255    ):
 256        """See Model.new_var below."""
 257        LinearExpr.__init__(self)
 258        self.__helper: mbh.ModelBuilderHelper = helper
 259        # Python do not support multiple __init__ methods.
 260        # This method is only called from the Model class.
 261        # We hack the parameter to support the two cases:
 262        # case 1:
 263        #     helper is a ModelBuilderHelper, lb is a double value, ub is a double
 264        #     value, is_integral is a Boolean value, and name is a string.
 265        # case 2:
 266        #     helper is a ModelBuilderHelper, lb is an index (int), ub is None,
 267        #     is_integral is None, and name is None.
 268        if mbn.is_integral(lb) and ub is None and is_integral is None:
 269            self.__index: np.int32 = np.int32(lb)
 270            self.__helper: mbh.ModelBuilderHelper = helper
 271        else:
 272            index: np.int32 = helper.add_var()
 273            self.__index: np.int32 = np.int32(index)
 274            self.__helper: mbh.ModelBuilderHelper = helper
 275            helper.set_var_lower_bound(index, lb)
 276            helper.set_var_upper_bound(index, ub)
 277            helper.set_var_integrality(index, is_integral)
 278            if name:
 279                helper.set_var_name(index, name)
 280
 281    @property
 282    def index(self) -> np.int32:
 283        """Returns the index of the variable in the helper."""
 284        return self.__index
 285
 286    @property
 287    def helper(self) -> mbh.ModelBuilderHelper:
 288        """Returns the underlying ModelBuilderHelper."""
 289        return self.__helper
 290
 291    def is_equal_to(self, other: LinearExprT) -> bool:
 292        """Returns true if self == other in the python sense."""
 293        if not isinstance(other, Variable):
 294            return False
 295        return self.index == other.index and self.helper == other.helper
 296
 297    def __str__(self) -> str:
 298        return self.name
 299
 300    def __repr__(self) -> str:
 301        return self.__str__()
 302
 303    @property
 304    def name(self) -> str:
 305        """Returns the name of the variable."""
 306        var_name = self.__helper.var_name(self.__index)
 307        if var_name:
 308            return var_name
 309        return f"variable#{self.index}"
 310
 311    @name.setter
 312    def name(self, name: str) -> None:
 313        """Sets the name of the variable."""
 314        self.__helper.set_var_name(self.__index, name)
 315
 316    @property
 317    def lower_bound(self) -> np.double:
 318        """Returns the lower bound of the variable."""
 319        return self.__helper.var_lower_bound(self.__index)
 320
 321    @lower_bound.setter
 322    def lower_bound(self, bound: NumberT) -> None:
 323        """Sets the lower bound of the variable."""
 324        self.__helper.set_var_lower_bound(self.__index, bound)
 325
 326    @property
 327    def upper_bound(self) -> np.double:
 328        """Returns the upper bound of the variable."""
 329        return self.__helper.var_upper_bound(self.__index)
 330
 331    @upper_bound.setter
 332    def upper_bound(self, bound: NumberT) -> None:
 333        """Sets the upper bound of the variable."""
 334        self.__helper.set_var_upper_bound(self.__index, bound)
 335
 336    @property
 337    def is_integral(self) -> bool:
 338        """Returns whether the variable is integral."""
 339        return self.__helper.var_is_integral(self.__index)
 340
 341    @is_integral.setter
 342    def integrality(self, is_integral: bool) -> None:
 343        """Sets the integrality of the variable."""
 344        self.__helper.set_var_integrality(self.__index, is_integral)
 345
 346    @property
 347    def objective_coefficient(self) -> NumberT:
 348        return self.__helper.var_objective_coefficient(self.__index)
 349
 350    @objective_coefficient.setter
 351    def objective_coefficient(self, coeff: NumberT) -> None:
 352        self.__helper.set_var_objective_coefficient(self.__index, coeff)
 353
 354    def __eq__(self, arg: Optional[LinearExprT]) -> ConstraintT:
 355        if arg is None:
 356            return False
 357        if isinstance(arg, Variable):
 358            return VarEqVar(self, arg)
 359        return BoundedLinearExpression(
 360            self - arg, 0.0, 0.0
 361        )  # pytype: disable=wrong-arg-types  # numpy-scalars
 362
 363    def __hash__(self):
 364        return hash((self.__helper, self.__index))
 365
 366
 367class _BoundedLinearExpr(metaclass=abc.ABCMeta):
 368    """Interface for types that can build bounded linear (boolean) expressions.
 369
 370    Classes derived from _BoundedLinearExpr are used to build linear constraints
 371    to be satisfied.
 372
 373      * BoundedLinearExpression: a linear expression with upper and lower bounds.
 374      * VarEqVar: an equality comparison between two variables.
 375    """
 376
 377    @abc.abstractmethod
 378    def _add_linear_constraint(
 379        self, helper: mbh.ModelBuilderHelper, name: str
 380    ) -> "LinearConstraint":
 381        """Creates a new linear constraint in the helper.
 382
 383        Args:
 384          helper (mbh.ModelBuilderHelper): The helper to create the constraint.
 385          name (str): The name of the linear constraint.
 386
 387        Returns:
 388          LinearConstraint: A reference to the linear constraint in the helper.
 389        """
 390
 391    @abc.abstractmethod
 392    def _add_enforced_linear_constraint(
 393        self,
 394        helper: mbh.ModelBuilderHelper,
 395        var: Variable,
 396        value: bool,
 397        name: str,
 398    ) -> "EnforcedLinearConstraint":
 399        """Creates a new enforced linear constraint in the helper.
 400
 401        Args:
 402          helper (mbh.ModelBuilderHelper): The helper to create the constraint.
 403          var (Variable): The indicator variable of the constraint.
 404          value (bool): The indicator value of the constraint.
 405          name (str): The name of the linear constraint.
 406
 407        Returns:
 408          Enforced LinearConstraint: A reference to the linear constraint in the
 409          helper.
 410        """
 411
 412
 413def _add_linear_constraint_to_helper(
 414    bounded_expr: Union[bool, _BoundedLinearExpr],
 415    helper: mbh.ModelBuilderHelper,
 416    name: Optional[str],
 417):
 418    """Creates a new linear constraint in the helper.
 419
 420    It handles boolean values (which might arise in the construction of
 421    BoundedLinearExpressions).
 422
 423    Args:
 424      bounded_expr: The bounded expression used to create the constraint.
 425      helper: The helper to create the constraint.
 426      name: The name of the constraint to be created.
 427
 428    Returns:
 429      LinearConstraint: a constraint in the helper corresponding to the input.
 430
 431    Raises:
 432      TypeError: If constraint is an invalid type.
 433    """
 434    if isinstance(bounded_expr, bool):
 435        c = LinearConstraint(helper)
 436        if name is not None:
 437            helper.set_constraint_name(c.index, name)
 438        if bounded_expr:
 439            # constraint that is always feasible: 0.0 <= nothing <= 0.0
 440            helper.set_constraint_lower_bound(c.index, 0.0)
 441            helper.set_constraint_upper_bound(c.index, 0.0)
 442        else:
 443            # constraint that is always infeasible: +oo <= nothing <= -oo
 444            helper.set_constraint_lower_bound(c.index, 1)
 445            helper.set_constraint_upper_bound(c.index, -1)
 446        return c
 447    if isinstance(bounded_expr, _BoundedLinearExpr):
 448        # pylint: disable=protected-access
 449        return bounded_expr._add_linear_constraint(helper, name)
 450    raise TypeError("invalid type={}".format(type(bounded_expr)))
 451
 452
 453def _add_enforced_linear_constraint_to_helper(
 454    bounded_expr: Union[bool, _BoundedLinearExpr],
 455    helper: mbh.ModelBuilderHelper,
 456    var: Variable,
 457    value: bool,
 458    name: Optional[str],
 459):
 460    """Creates a new enforced linear constraint in the helper.
 461
 462    It handles boolean values (which might arise in the construction of
 463    BoundedLinearExpressions).
 464
 465    Args:
 466      bounded_expr: The bounded expression used to create the constraint.
 467      helper: The helper to create the constraint.
 468      var: the variable used in the indicator
 469      value: the value used in the indicator
 470      name: The name of the constraint to be created.
 471
 472    Returns:
 473      EnforcedLinearConstraint: a constraint in the helper corresponding to the
 474      input.
 475
 476    Raises:
 477      TypeError: If constraint is an invalid type.
 478    """
 479    if isinstance(bounded_expr, bool):
 480        c = EnforcedLinearConstraint(helper)
 481        c.indicator_variable = var
 482        c.indicator_value = value
 483        if name is not None:
 484            helper.set_enforced_constraint_name(c.index, name)
 485        if bounded_expr:
 486            # constraint that is always feasible: 0.0 <= nothing <= 0.0
 487            helper.set_enforced_constraint_lower_bound(c.index, 0.0)
 488            helper.set_enforced_constraint_upper_bound(c.index, 0.0)
 489        else:
 490            # constraint that is always infeasible: +oo <= nothing <= -oo
 491            helper.set_enforced_constraint_lower_bound(c.index, 1)
 492            helper.set_enforced_constraint_upper_bound(c.index, -1)
 493        return c
 494    if isinstance(bounded_expr, _BoundedLinearExpr):
 495        # pylint: disable=protected-access
 496        return bounded_expr._add_enforced_linear_constraint(helper, var, value, name)
 497    raise TypeError("invalid type={}".format(type(bounded_expr)))
 498
 499
 500@dataclasses.dataclass(repr=False, eq=False, frozen=True)
 501class VarEqVar(_BoundedLinearExpr):
 502    """Represents var == var."""
 503
 504    __slots__ = ("left", "right")
 505
 506    left: Variable
 507    right: Variable
 508
 509    def __str__(self):
 510        return f"{self.left} == {self.right}"
 511
 512    def __repr__(self):
 513        return self.__str__()
 514
 515    def __bool__(self) -> bool:
 516        return hash(self.left) == hash(self.right)
 517
 518    def _add_linear_constraint(
 519        self, helper: mbh.ModelBuilderHelper, name: str
 520    ) -> "LinearConstraint":
 521        c = LinearConstraint(helper)
 522        helper.set_constraint_lower_bound(c.index, 0.0)
 523        helper.set_constraint_upper_bound(c.index, 0.0)
 524        # pylint: disable=protected-access
 525        helper.add_term_to_constraint(c.index, self.left.index, 1.0)
 526        helper.add_term_to_constraint(c.index, self.right.index, -1.0)
 527        # pylint: enable=protected-access
 528        helper.set_constraint_name(c.index, name)
 529        return c
 530
 531    def _add_enforced_linear_constraint(
 532        self,
 533        helper: mbh.ModelBuilderHelper,
 534        var: Variable,
 535        value: bool,
 536        name: str,
 537    ) -> "EnforcedLinearConstraint":
 538        """Adds an enforced linear constraint to the model."""
 539        c = EnforcedLinearConstraint(helper)
 540        c.indicator_variable = var
 541        c.indicator_value = value
 542        helper.set_enforced_constraint_lower_bound(c.index, 0.0)
 543        helper.set_enforced_constraint_upper_bound(c.index, 0.0)
 544        # pylint: disable=protected-access
 545        helper.add_term_to_enforced_constraint(c.index, self.left.index, 1.0)
 546        helper.add_term_to_enforced_constraint(c.index, self.right.index, -1.0)
 547        # pylint: enable=protected-access
 548        helper.set_enforced_constraint_name(c.index, name)
 549        return c
 550
 551
 552class BoundedLinearExpression(_BoundedLinearExpr):
 553    """Represents a linear constraint: `lb <= linear expression <= ub`.
 554
 555    The only use of this class is to be added to the Model through
 556    `Model.add(bounded expression)`, as in:
 557
 558        model.Add(x + 2 * y -1 >= z)
 559    """
 560
 561    def __init__(self, expr: LinearExprT, lb: NumberT, ub: NumberT):
 562        self.__expr: LinearExprT = expr
 563        self.__lb: np.double = mbn.assert_is_a_number(lb)
 564        self.__ub: np.double = mbn.assert_is_a_number(ub)
 565
 566    def __str__(self) -> str:
 567        if self.__lb > -math.inf and self.__ub < math.inf:
 568            if self.__lb == self.__ub:
 569                return f"{self.__expr} == {self.__lb}"
 570            else:
 571                return f"{self.__lb} <= {self.__expr} <= {self.__ub}"
 572        elif self.__lb > -math.inf:
 573            return f"{self.__expr} >= {self.__lb}"
 574        elif self.__ub < math.inf:
 575            return f"{self.__expr} <= {self.__ub}"
 576        else:
 577            return f"{self.__expr} free"
 578
 579    def __repr__(self):
 580        return self.__str__()
 581
 582    @property
 583    def expression(self) -> LinearExprT:
 584        return self.__expr
 585
 586    @property
 587    def lower_bound(self) -> np.double:
 588        return self.__lb
 589
 590    @property
 591    def upper_bound(self) -> np.double:
 592        return self.__ub
 593
 594    def __bool__(self) -> bool:
 595        raise NotImplementedError(
 596            f"Cannot use a BoundedLinearExpression {self} as a Boolean value"
 597        )
 598
 599    def _add_linear_constraint(
 600        self, helper: mbh.ModelBuilderHelper, name: Optional[str]
 601    ) -> "LinearConstraint":
 602        c = LinearConstraint(helper)
 603        flat_expr = _as_flat_linear_expression(self.__expr)
 604        # pylint: disable=protected-access
 605        helper.add_terms_to_constraint(
 606            c.index, flat_expr._variable_indices, flat_expr._coefficients
 607        )
 608        helper.set_constraint_lower_bound(c.index, self.__lb - flat_expr._offset)
 609        helper.set_constraint_upper_bound(c.index, self.__ub - flat_expr._offset)
 610        # pylint: enable=protected-access
 611        if name is not None:
 612            helper.set_constraint_name(c.index, name)
 613        return c
 614
 615    def _add_enforced_linear_constraint(
 616        self,
 617        helper: mbh.ModelBuilderHelper,
 618        var: Variable,
 619        value: bool,
 620        name: Optional[str],
 621    ) -> "EnforcedLinearConstraint":
 622        """Adds an enforced linear constraint to the model."""
 623        c = EnforcedLinearConstraint(helper)
 624        c.indicator_variable = var
 625        c.indicator_value = value
 626        flat_expr = _as_flat_linear_expression(self.__expr)
 627        # pylint: disable=protected-access
 628        helper.add_terms_to_enforced_constraint(
 629            c.index, flat_expr._variable_indices, flat_expr._coefficients
 630        )
 631        helper.set_enforced_constraint_lower_bound(
 632            c.index, self.__lb - flat_expr._offset
 633        )
 634        helper.set_enforced_constraint_upper_bound(
 635            c.index, self.__ub - flat_expr._offset
 636        )
 637        # pylint: enable=protected-access
 638        if name is not None:
 639            helper.set_enforced_constraint_name(c.index, name)
 640        return c
 641
 642
 643class LinearConstraint:
 644    """Stores a linear equation.
 645
 646    Example:
 647        x = model.new_num_var(0, 10, 'x')
 648        y = model.new_num_var(0, 10, 'y')
 649
 650        linear_constraint = model.add(x + 2 * y == 5)
 651    """
 652
 653    def __init__(
 654        self, helper: mbh.ModelBuilderHelper, index: Optional[IntegerT] = None
 655    ):
 656        if index is None:
 657            self.__index = helper.add_linear_constraint()
 658        else:
 659            self.__index = index
 660        self.__helper: mbh.ModelBuilderHelper = helper
 661
 662    @property
 663    def index(self) -> IntegerT:
 664        """Returns the index of the constraint in the helper."""
 665        return self.__index
 666
 667    @property
 668    def helper(self) -> mbh.ModelBuilderHelper:
 669        """Returns the ModelBuilderHelper instance."""
 670        return self.__helper
 671
 672    @property
 673    def lower_bound(self) -> np.double:
 674        return self.__helper.constraint_lower_bound(self.__index)
 675
 676    @lower_bound.setter
 677    def lower_bound(self, bound: NumberT) -> None:
 678        self.__helper.set_constraint_lower_bound(self.__index, bound)
 679
 680    @property
 681    def upper_bound(self) -> np.double:
 682        return self.__helper.constraint_upper_bound(self.__index)
 683
 684    @upper_bound.setter
 685    def upper_bound(self, bound: NumberT) -> None:
 686        self.__helper.set_constraint_upper_bound(self.__index, bound)
 687
 688    @property
 689    def name(self) -> str:
 690        constraint_name = self.__helper.constraint_name(self.__index)
 691        if constraint_name:
 692            return constraint_name
 693        return f"linear_constraint#{self.__index}"
 694
 695    @name.setter
 696    def name(self, name: str) -> None:
 697        return self.__helper.set_constraint_name(self.__index, name)
 698
 699    def is_always_false(self) -> bool:
 700        """Returns True if the constraint is always false.
 701
 702        Usually, it means that it was created by model.add(False)
 703        """
 704        return self.lower_bound > self.upper_bound
 705
 706    def __str__(self):
 707        return self.name
 708
 709    def __repr__(self):
 710        return (
 711            f"LinearConstraint({self.name}, lb={self.lower_bound},"
 712            f" ub={self.upper_bound},"
 713            f" var_indices={self.helper.constraint_var_indices(self.index)},"
 714            f" coefficients={self.helper.constraint_coefficients(self.index)})"
 715        )
 716
 717    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
 718        """Sets the coefficient of the variable in the constraint."""
 719        if self.is_always_false():
 720            raise ValueError(
 721                f"Constraint {self.index} is always false and cannot be modified"
 722            )
 723        self.__helper.set_constraint_coefficient(self.__index, var.index, coeff)
 724
 725    def add_term(self, var: Variable, coeff: NumberT) -> None:
 726        """Adds var * coeff to the constraint."""
 727        if self.is_always_false():
 728            raise ValueError(
 729                f"Constraint {self.index} is always false and cannot be modified"
 730            )
 731        self.__helper.safe_add_term_to_constraint(self.__index, var.index, coeff)
 732
 733    def clear_terms(self) -> None:
 734        """Clear all terms of the constraint."""
 735        self.__helper.clear_constraint_terms(self.__index)
 736
 737
 738class EnforcedLinearConstraint:
 739    """Stores an enforced linear equation, also name indicator constraint.
 740
 741    Example:
 742        x = model.new_num_var(0, 10, 'x')
 743        y = model.new_num_var(0, 10, 'y')
 744        z = model.new_bool_var('z')
 745
 746        enforced_linear_constraint = model.add_enforced(x + 2 * y == 5, z, False)
 747    """
 748
 749    def __init__(
 750        self, helper: mbh.ModelBuilderHelper, index: Optional[IntegerT] = None
 751    ):
 752        if index is None:
 753            self.__index = helper.add_enforced_linear_constraint()
 754        else:
 755            if not helper.is_enforced_linear_constraint(index):
 756                raise ValueError(
 757                    f"the given index {index} does not refer to an enforced linear"
 758                    " constraint"
 759                )
 760
 761            self.__index = index
 762        self.__helper: mbh.ModelBuilderHelper = helper
 763
 764    @property
 765    def index(self) -> IntegerT:
 766        """Returns the index of the constraint in the helper."""
 767        return self.__index
 768
 769    @property
 770    def helper(self) -> mbh.ModelBuilderHelper:
 771        """Returns the ModelBuilderHelper instance."""
 772        return self.__helper
 773
 774    @property
 775    def lower_bound(self) -> np.double:
 776        return self.__helper.enforced_constraint_lower_bound(self.__index)
 777
 778    @lower_bound.setter
 779    def lower_bound(self, bound: NumberT) -> None:
 780        self.__helper.set_enforced_constraint_lower_bound(self.__index, bound)
 781
 782    @property
 783    def upper_bound(self) -> np.double:
 784        return self.__helper.enforced_constraint_upper_bound(self.__index)
 785
 786    @upper_bound.setter
 787    def upper_bound(self, bound: NumberT) -> None:
 788        self.__helper.set_enforced_constraint_upper_bound(self.__index, bound)
 789
 790    @property
 791    def indicator_variable(self) -> "Variable":
 792        enforcement_var_index = (
 793            self.__helper.enforced_constraint_indicator_variable_index(self.__index)
 794        )
 795        return Variable(self.__helper, enforcement_var_index, None, None, None)
 796
 797    @indicator_variable.setter
 798    def indicator_variable(self, var: "Variable") -> None:
 799        self.__helper.set_enforced_constraint_indicator_variable_index(
 800            self.__index, var.index
 801        )
 802
 803    @property
 804    def indicator_value(self) -> bool:
 805        return self.__helper.enforced_constraint_indicator_value(self.__index)
 806
 807    @indicator_value.setter
 808    def indicator_value(self, value: bool) -> None:
 809        self.__helper.set_enforced_constraint_indicator_value(self.__index, value)
 810
 811    @property
 812    def name(self) -> str:
 813        constraint_name = self.__helper.enforced_constraint_name(self.__index)
 814        if constraint_name:
 815            return constraint_name
 816        return f"enforced_linear_constraint#{self.__index}"
 817
 818    @name.setter
 819    def name(self, name: str) -> None:
 820        return self.__helper.set_enforced_constraint_name(self.__index, name)
 821
 822    def is_always_false(self) -> bool:
 823        """Returns True if the constraint is always false.
 824
 825        Usually, it means that it was created by model.add(False)
 826        """
 827        return self.lower_bound > self.upper_bound
 828
 829    def __str__(self):
 830        return self.name
 831
 832    def __repr__(self):
 833        return (
 834            f"EnforcedLinearConstraint({self.name}, lb={self.lower_bound},"
 835            f" ub={self.upper_bound},"
 836            f" var_indices={self.helper.enforced_constraint_var_indices(self.index)},"
 837            f" coefficients={self.helper.enforced_constraint_coefficients(self.index)},"
 838            f" indicator_variable={self.indicator_variable}"
 839            f" indicator_value={self.indicator_value})"
 840        )
 841
 842    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
 843        """Sets the coefficient of the variable in the constraint."""
 844        if self.is_always_false():
 845            raise ValueError(
 846                f"Constraint {self.index} is always false and cannot be modified"
 847            )
 848        self.__helper.set_enforced_constraint_coefficient(
 849            self.__index, var.index, coeff
 850        )
 851
 852    def add_term(self, var: Variable, coeff: NumberT) -> None:
 853        """Adds var * coeff to the constraint."""
 854        if self.is_always_false():
 855            raise ValueError(
 856                f"Constraint {self.index} is always false and cannot be modified"
 857            )
 858        self.__helper.safe_add_term_to_enforced_constraint(
 859            self.__index, var.index, coeff
 860        )
 861
 862    def clear_terms(self) -> None:
 863        """Clear all terms of the constraint."""
 864        self.__helper.clear_enforced_constraint_terms(self.__index)
 865
 866
 867class Model:
 868    """Methods for building a linear model.
 869
 870    Methods beginning with:
 871
 872    * ```new_``` create integer, boolean, or interval variables.
 873    * ```add_``` create new constraints and add them to the model.
 874    """
 875
 876    def __init__(self):
 877        self.__helper: mbh.ModelBuilderHelper = mbh.ModelBuilderHelper()
 878
 879    def clone(self) -> "Model":
 880        """Returns a clone of the current model."""
 881        clone = Model()
 882        clone.helper.overwrite_model(self.helper)
 883        return clone
 884
 885    @typing.overload
 886    def _get_linear_constraints(self, constraints: Optional[pd.Index]) -> pd.Index:
 887        ...
 888
 889    @typing.overload
 890    def _get_linear_constraints(self, constraints: pd.Series) -> pd.Series:
 891        ...
 892
 893    def _get_linear_constraints(
 894        self, constraints: Optional[_IndexOrSeries] = None
 895    ) -> _IndexOrSeries:
 896        if constraints is None:
 897            return self.get_linear_constraints()
 898        return constraints
 899
 900    @typing.overload
 901    def _get_variables(self, variables: Optional[pd.Index]) -> pd.Index:
 902        ...
 903
 904    @typing.overload
 905    def _get_variables(self, variables: pd.Series) -> pd.Series:
 906        ...
 907
 908    def _get_variables(
 909        self, variables: Optional[_IndexOrSeries] = None
 910    ) -> _IndexOrSeries:
 911        if variables is None:
 912            return self.get_variables()
 913        return variables
 914
 915    def get_linear_constraints(self) -> pd.Index:
 916        """Gets all linear constraints in the model."""
 917        return pd.Index(
 918            [self.linear_constraint_from_index(i) for i in range(self.num_constraints)],
 919            name="linear_constraint",
 920        )
 921
 922    def get_linear_constraint_expressions(
 923        self, constraints: Optional[_IndexOrSeries] = None
 924    ) -> pd.Series:
 925        """Gets the expressions of all linear constraints in the set.
 926
 927        If `constraints` is a `pd.Index`, then the output will be indexed by the
 928        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 929        dimensions, then the output will be indexed by the same underlying
 930        dimensions.
 931
 932        Args:
 933          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 934            constraints from which to get the expressions. If unspecified, all
 935            linear constraints will be in scope.
 936
 937        Returns:
 938          pd.Series: The expressions of all linear constraints in the set.
 939        """
 940        return _attribute_series(
 941            # pylint: disable=g-long-lambda
 942            func=lambda c: _as_flat_linear_expression(
 943                # pylint: disable=g-complex-comprehension
 944                sum(
 945                    coeff * Variable(self.__helper, var_id, None, None, None)
 946                    for var_id, coeff in zip(
 947                        c.helper.constraint_var_indices(c.index),
 948                        c.helper.constraint_coefficients(c.index),
 949                    )
 950                )
 951            ),
 952            values=self._get_linear_constraints(constraints),
 953        )
 954
 955    def get_linear_constraint_lower_bounds(
 956        self, constraints: Optional[_IndexOrSeries] = None
 957    ) -> pd.Series:
 958        """Gets the lower bounds of all linear constraints in the set.
 959
 960        If `constraints` is a `pd.Index`, then the output will be indexed by the
 961        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 962        dimensions, then the output will be indexed by the same underlying
 963        dimensions.
 964
 965        Args:
 966          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 967            constraints from which to get the lower bounds. If unspecified, all
 968            linear constraints will be in scope.
 969
 970        Returns:
 971          pd.Series: The lower bounds of all linear constraints in the set.
 972        """
 973        return _attribute_series(
 974            func=lambda c: c.lower_bound,  # pylint: disable=protected-access
 975            values=self._get_linear_constraints(constraints),
 976        )
 977
 978    def get_linear_constraint_upper_bounds(
 979        self, constraints: Optional[_IndexOrSeries] = None
 980    ) -> pd.Series:
 981        """Gets the upper bounds of all linear constraints in the set.
 982
 983        If `constraints` is a `pd.Index`, then the output will be indexed by the
 984        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 985        dimensions, then the output will be indexed by the same underlying
 986        dimensions.
 987
 988        Args:
 989          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 990            constraints. If unspecified, all linear constraints will be in scope.
 991
 992        Returns:
 993          pd.Series: The upper bounds of all linear constraints in the set.
 994        """
 995        return _attribute_series(
 996            func=lambda c: c.upper_bound,  # pylint: disable=protected-access
 997            values=self._get_linear_constraints(constraints),
 998        )
 999
1000    def get_variables(self) -> pd.Index:
1001        """Gets all variables in the model."""
1002        return pd.Index(
1003            [self.var_from_index(i) for i in range(self.num_variables)],
1004            name="variable",
1005        )
1006
1007    def get_variable_lower_bounds(
1008        self, variables: Optional[_IndexOrSeries] = None
1009    ) -> pd.Series:
1010        """Gets the lower bounds of all variables in the set.
1011
1012        If `variables` is a `pd.Index`, then the output will be indexed by the
1013        variables. If `variables` is a `pd.Series` indexed by the underlying
1014        dimensions, then the output will be indexed by the same underlying
1015        dimensions.
1016
1017        Args:
1018          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
1019            from which to get the lower bounds. If unspecified, all variables will
1020            be in scope.
1021
1022        Returns:
1023          pd.Series: The lower bounds of all variables in the set.
1024        """
1025        return _attribute_series(
1026            func=lambda v: v.lower_bound,  # pylint: disable=protected-access
1027            values=self._get_variables(variables),
1028        )
1029
1030    def get_variable_upper_bounds(
1031        self, variables: Optional[_IndexOrSeries] = None
1032    ) -> pd.Series:
1033        """Gets the upper bounds of all variables in the set.
1034
1035        Args:
1036          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
1037            from which to get the upper bounds. If unspecified, all variables will
1038            be in scope.
1039
1040        Returns:
1041          pd.Series: The upper bounds of all variables in the set.
1042        """
1043        return _attribute_series(
1044            func=lambda v: v.upper_bound,  # pylint: disable=protected-access
1045            values=self._get_variables(variables),
1046        )
1047
1048    # Integer variable.
1049
1050    def new_var(
1051        self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str]
1052    ) -> Variable:
1053        """Create an integer variable with domain [lb, ub].
1054
1055        Args:
1056          lb: Lower bound of the variable.
1057          ub: Upper bound of the variable.
1058          is_integer: Indicates if the variable must take integral values.
1059          name: The name of the variable.
1060
1061        Returns:
1062          a variable whose domain is [lb, ub].
1063        """
1064
1065        return Variable(self.__helper, lb, ub, is_integer, name)
1066
1067    def new_int_var(
1068        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
1069    ) -> Variable:
1070        """Create an integer variable with domain [lb, ub].
1071
1072        Args:
1073          lb: Lower bound of the variable.
1074          ub: Upper bound of the variable.
1075          name: The name of the variable.
1076
1077        Returns:
1078          a variable whose domain is [lb, ub].
1079        """
1080
1081        return self.new_var(lb, ub, True, name)
1082
1083    def new_num_var(
1084        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
1085    ) -> Variable:
1086        """Create an integer variable with domain [lb, ub].
1087
1088        Args:
1089          lb: Lower bound of the variable.
1090          ub: Upper bound of the variable.
1091          name: The name of the variable.
1092
1093        Returns:
1094          a variable whose domain is [lb, ub].
1095        """
1096
1097        return self.new_var(lb, ub, False, name)
1098
1099    def new_bool_var(self, name: Optional[str] = None) -> Variable:
1100        """Creates a 0-1 variable with the given name."""
1101        return self.new_var(
1102            0, 1, True, name
1103        )  # pytype: disable=wrong-arg-types  # numpy-scalars
1104
1105    def new_constant(self, value: NumberT) -> Variable:
1106        """Declares a constant variable."""
1107        return self.new_var(value, value, False, None)
1108
1109    def new_var_series(
1110        self,
1111        name: str,
1112        index: pd.Index,
1113        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1114        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1115        is_integral: Union[bool, pd.Series] = False,
1116    ) -> pd.Series:
1117        """Creates a series of (scalar-valued) variables with the given name.
1118
1119        Args:
1120          name (str): Required. The name of the variable set.
1121          index (pd.Index): Required. The index to use for the variable set.
1122          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1123            variables in the set. If a `pd.Series` is passed in, it will be based on
1124            the corresponding values of the pd.Series. Defaults to -inf.
1125          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1126            variables in the set. If a `pd.Series` is passed in, it will be based on
1127            the corresponding values of the pd.Series. Defaults to +inf.
1128          is_integral (bool, pd.Series): Optional. Indicates if the variable can
1129            only take integer values. If a `pd.Series` is passed in, it will be
1130            based on the corresponding values of the pd.Series. Defaults to False.
1131
1132        Returns:
1133          pd.Series: The variable set indexed by its corresponding dimensions.
1134
1135        Raises:
1136          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1137          ValueError: if the `name` is not a valid identifier or already exists.
1138          ValueError: if the `lowerbound` is greater than the `upperbound`.
1139          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1140          does not match the input index.
1141        """
1142        if not isinstance(index, pd.Index):
1143            raise TypeError("Non-index object is used as index")
1144        if not name.isidentifier():
1145            raise ValueError("name={} is not a valid identifier".format(name))
1146        if (
1147            mbn.is_a_number(lower_bounds)
1148            and mbn.is_a_number(upper_bounds)
1149            and lower_bounds > upper_bounds
1150        ):
1151            raise ValueError(
1152                "lower_bound={} is greater than upper_bound={} for variable set={}".format(
1153                    lower_bounds, upper_bounds, name
1154                )
1155            )
1156        if (
1157            isinstance(is_integral, bool)
1158            and is_integral
1159            and mbn.is_a_number(lower_bounds)
1160            and mbn.is_a_number(upper_bounds)
1161            and math.isfinite(lower_bounds)
1162            and math.isfinite(upper_bounds)
1163            and math.ceil(lower_bounds) > math.floor(upper_bounds)
1164        ):
1165            raise ValueError(
1166                "ceil(lower_bound={})={}".format(lower_bounds, math.ceil(lower_bounds))
1167                + " is greater than floor("
1168                + "upper_bound={})={}".format(upper_bounds, math.floor(upper_bounds))
1169                + " for variable set={}".format(name)
1170            )
1171        lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index)
1172        upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index)
1173        is_integrals = _convert_to_series_and_validate_index(is_integral, index)
1174        return pd.Series(
1175            index=index,
1176            data=[
1177                # pylint: disable=g-complex-comprehension
1178                Variable(
1179                    helper=self.__helper,
1180                    name=f"{name}[{i}]",
1181                    lb=lower_bounds[i],
1182                    ub=upper_bounds[i],
1183                    is_integral=is_integrals[i],
1184                )
1185                for i in index
1186            ],
1187        )
1188
1189    def new_num_var_series(
1190        self,
1191        name: str,
1192        index: pd.Index,
1193        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1194        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1195    ) -> pd.Series:
1196        """Creates a series of continuous 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          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1202            variables in the set. If a `pd.Series` is passed in, it will be based on
1203            the corresponding values of the pd.Series. Defaults to -inf.
1204          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1205            variables in the set. If a `pd.Series` is passed in, it will be based on
1206            the corresponding values of the pd.Series. Defaults to +inf.
1207
1208        Returns:
1209          pd.Series: The variable set indexed by its corresponding 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 `lowerbound` is greater than the `upperbound`.
1215          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1216          does not match the input index.
1217        """
1218        return self.new_var_series(name, index, lower_bounds, upper_bounds, False)
1219
1220    def new_int_var_series(
1221        self,
1222        name: str,
1223        index: pd.Index,
1224        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1225        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1226    ) -> pd.Series:
1227        """Creates a series of integer variables with the given name.
1228
1229        Args:
1230          name (str): Required. The name of the variable set.
1231          index (pd.Index): Required. The index to use for the variable set.
1232          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1233            variables in the set. If a `pd.Series` is passed in, it will be based on
1234            the corresponding values of the pd.Series. Defaults to -inf.
1235          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1236            variables in the set. If a `pd.Series` is passed in, it will be based on
1237            the corresponding values of the pd.Series. Defaults to +inf.
1238
1239        Returns:
1240          pd.Series: The variable set indexed by its corresponding dimensions.
1241
1242        Raises:
1243          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1244          ValueError: if the `name` is not a valid identifier or already exists.
1245          ValueError: if the `lowerbound` is greater than the `upperbound`.
1246          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1247          does not match the input index.
1248        """
1249        return self.new_var_series(name, index, lower_bounds, upper_bounds, True)
1250
1251    def new_bool_var_series(
1252        self,
1253        name: str,
1254        index: pd.Index,
1255    ) -> pd.Series:
1256        """Creates a series of Boolean variables with the given name.
1257
1258        Args:
1259          name (str): Required. The name of the variable set.
1260          index (pd.Index): Required. The index to use for the variable set.
1261
1262        Returns:
1263          pd.Series: The variable set indexed by its corresponding dimensions.
1264
1265        Raises:
1266          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1267          ValueError: if the `name` is not a valid identifier or already exists.
1268          ValueError: if the `lowerbound` is greater than the `upperbound`.
1269          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1270          does not match the input index.
1271        """
1272        return self.new_var_series(name, index, 0, 1, True)
1273
1274    def var_from_index(self, index: IntegerT) -> Variable:
1275        """Rebuilds a variable object from the model and its index."""
1276        return Variable(self.__helper, index, None, None, None)
1277
1278    # Linear constraints.
1279
1280    def add_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
1281        self,
1282        linear_expr: LinearExprT,
1283        lb: NumberT = -math.inf,
1284        ub: NumberT = math.inf,
1285        name: Optional[str] = None,
1286    ) -> LinearConstraint:
1287        """Adds the constraint: `lb <= linear_expr <= ub` with the given name."""
1288        ct = LinearConstraint(self.__helper)
1289        if name:
1290            self.__helper.set_constraint_name(ct.index, name)
1291        if mbn.is_a_number(linear_expr):
1292            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
1293            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
1294        elif isinstance(linear_expr, Variable):
1295            self.__helper.set_constraint_lower_bound(ct.index, lb)
1296            self.__helper.set_constraint_upper_bound(ct.index, ub)
1297            self.__helper.add_term_to_constraint(ct.index, linear_expr.index, 1.0)
1298        elif isinstance(linear_expr, LinearExpr):
1299            flat_expr = _as_flat_linear_expression(linear_expr)
1300            # pylint: disable=protected-access
1301            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr._offset)
1302            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr._offset)
1303            self.__helper.add_terms_to_constraint(
1304                ct.index, flat_expr._variable_indices, flat_expr._coefficients
1305            )
1306        else:
1307            raise TypeError(
1308                f"Not supported: Model.add_linear_constraint({linear_expr})"
1309                f" with type {type(linear_expr)}"
1310            )
1311        return ct
1312
1313    def add(
1314        self, ct: Union[ConstraintT, pd.Series], name: Optional[str] = None
1315    ) -> Union[LinearConstraint, pd.Series]:
1316        """Adds a `BoundedLinearExpression` to the model.
1317
1318        Args:
1319          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
1320          name: An optional name.
1321
1322        Returns:
1323          An instance of the `Constraint` class.
1324
1325        Note that a special treatment is done when the argument does not contain any
1326        variable, and thus evaluates to True or False.
1327
1328        model.add(True) will create a constraint 0 <= empty sum <= 0
1329
1330        model.add(False) will create a constraint inf <= empty sum <= -inf
1331
1332        you can check the if a constraint is always false (lb=inf, ub=-inf) by
1333        calling LinearConstraint.is_always_false()
1334        """
1335        if isinstance(ct, _BoundedLinearExpr):
1336            return ct._add_linear_constraint(self.__helper, name)
1337        elif isinstance(ct, bool):
1338            return _add_linear_constraint_to_helper(ct, self.__helper, name)
1339        elif isinstance(ct, pd.Series):
1340            return pd.Series(
1341                index=ct.index,
1342                data=[
1343                    _add_linear_constraint_to_helper(
1344                        expr, self.__helper, f"{name}[{i}]"
1345                    )
1346                    for (i, expr) in zip(ct.index, ct)
1347                ],
1348            )
1349        else:
1350            raise TypeError("Not supported: Model.add(" + str(ct) + ")")
1351
1352    def linear_constraint_from_index(self, index: IntegerT) -> LinearConstraint:
1353        """Rebuilds a linear constraint object from the model and its index."""
1354        return LinearConstraint(self.__helper, index)
1355
1356    # EnforcedLinear constraints.
1357
1358    def add_enforced_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
1359        self,
1360        linear_expr: LinearExprT,
1361        ivar: "Variable",
1362        ivalue: bool,
1363        lb: NumberT = -math.inf,
1364        ub: NumberT = math.inf,
1365        name: Optional[str] = None,
1366    ) -> EnforcedLinearConstraint:
1367        """Adds the constraint: `ivar == ivalue => lb <= linear_expr <= ub` with the given name."""
1368        ct = EnforcedLinearConstraint(self.__helper)
1369        ct.indicator_variable = ivar
1370        ct.indicator_value = ivalue
1371        if name:
1372            self.__helper.set_constraint_name(ct.index, name)
1373        if mbn.is_a_number(linear_expr):
1374            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
1375            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
1376        elif isinstance(linear_expr, Variable):
1377            self.__helper.set_constraint_lower_bound(ct.index, lb)
1378            self.__helper.set_constraint_upper_bound(ct.index, ub)
1379            self.__helper.add_term_to_constraint(ct.index, linear_expr.index, 1.0)
1380        elif isinstance(linear_expr, LinearExpr):
1381            flat_expr = _as_flat_linear_expression(linear_expr)
1382            # pylint: disable=protected-access
1383            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr._offset)
1384            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr._offset)
1385            self.__helper.add_terms_to_constraint(
1386                ct.index, flat_expr._variable_indices, flat_expr._coefficients
1387            )
1388        else:
1389            raise TypeError(
1390                "Not supported:"
1391                f" Model.add_enforced_linear_constraint({linear_expr}) with"
1392                f" type {type(linear_expr)}"
1393            )
1394        return ct
1395
1396    def add_enforced(
1397        self,
1398        ct: Union[ConstraintT, pd.Series],
1399        var: Union[Variable, pd.Series],
1400        value: Union[bool, pd.Series],
1401        name: Optional[str] = None,
1402    ) -> Union[EnforcedLinearConstraint, pd.Series]:
1403        """Adds a `ivar == ivalue => BoundedLinearExpression` to the model.
1404
1405        Args:
1406          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
1407          var: The indicator variable
1408          value: the indicator value
1409          name: An optional name.
1410
1411        Returns:
1412          An instance of the `Constraint` class.
1413
1414        Note that a special treatment is done when the argument does not contain any
1415        variable, and thus evaluates to True or False.
1416
1417        model.add_enforced(True, ivar, ivalue) will create a constraint 0 <= empty
1418        sum <= 0
1419
1420        model.add_enforced(False, var, value) will create a constraint inf <=
1421        empty sum <= -inf
1422
1423        you can check the if a constraint is always false (lb=inf, ub=-inf) by
1424        calling EnforcedLinearConstraint.is_always_false()
1425        """
1426        if isinstance(ct, _BoundedLinearExpr):
1427            return ct._add_enforced_linear_constraint(self.__helper, var, value, name)
1428        elif (
1429            isinstance(ct, bool)
1430            and isinstance(var, Variable)
1431            and isinstance(value, bool)
1432        ):
1433            return _add_enforced_linear_constraint_to_helper(
1434                ct, self.__helper, var, value, name
1435            )
1436        elif isinstance(ct, pd.Series):
1437            ivar_series = _convert_to_var_series_and_validate_index(var, ct.index)
1438            ivalue_series = _convert_to_series_and_validate_index(value, ct.index)
1439            return pd.Series(
1440                index=ct.index,
1441                data=[
1442                    _add_enforced_linear_constraint_to_helper(
1443                        expr,
1444                        self.__helper,
1445                        ivar_series[i],
1446                        ivalue_series[i],
1447                        f"{name}[{i}]",
1448                    )
1449                    for (i, expr) in zip(ct.index, ct)
1450                ],
1451            )
1452        else:
1453            raise TypeError("Not supported: Model.add_enforced(" + str(ct) + ")")
1454
1455    def enforced_linear_constraint_from_index(
1456        self, index: IntegerT
1457    ) -> EnforcedLinearConstraint:
1458        """Rebuilds an enforced linear constraint object from the model and its index."""
1459        return EnforcedLinearConstraint(self.__helper, index)
1460
1461    # Objective.
1462    def minimize(self, linear_expr: LinearExprT) -> None:
1463        """Minimizes the given objective."""
1464        self.__optimize(linear_expr, False)
1465
1466    def maximize(self, linear_expr: LinearExprT) -> None:
1467        """Maximizes the given objective."""
1468        self.__optimize(linear_expr, True)
1469
1470    def __optimize(self, linear_expr: LinearExprT, maximize: bool) -> None:
1471        """Defines the objective."""
1472        self.helper.clear_objective()
1473        self.__helper.set_maximize(maximize)
1474        if mbn.is_a_number(linear_expr):
1475            self.helper.set_objective_offset(linear_expr)
1476        elif isinstance(linear_expr, Variable):
1477            self.helper.set_var_objective_coefficient(linear_expr.index, 1.0)
1478        elif isinstance(linear_expr, LinearExpr):
1479            flat_expr = _as_flat_linear_expression(linear_expr)
1480            # pylint: disable=protected-access
1481            self.helper.set_objective_offset(flat_expr._offset)
1482            self.helper.set_objective_coefficients(
1483                flat_expr._variable_indices, flat_expr._coefficients
1484            )
1485        else:
1486            raise TypeError(f"Not supported: Model.minimize/maximize({linear_expr})")
1487
1488    @property
1489    def objective_offset(self) -> np.double:
1490        """Returns the fixed offset of the objective."""
1491        return self.__helper.objective_offset()
1492
1493    @objective_offset.setter
1494    def objective_offset(self, value: NumberT) -> None:
1495        self.__helper.set_objective_offset(value)
1496
1497    def objective_expression(self) -> "_LinearExpression":
1498        """Returns the expression to optimize."""
1499        return _as_flat_linear_expression(
1500            sum(
1501                variable * self.__helper.var_objective_coefficient(variable.index)
1502                for variable in self.get_variables()
1503                if self.__helper.var_objective_coefficient(variable.index) != 0.0
1504            )
1505            + self.__helper.objective_offset()
1506        )
1507
1508    # Hints.
1509    def clear_hints(self):
1510        """Clears all solution hints."""
1511        self.__helper.clear_hints()
1512
1513    def add_hint(self, var: Variable, value: NumberT) -> None:
1514        """Adds var == value as a hint to the model.
1515
1516        Args:
1517          var: The variable of the hint
1518          value: The value of the hint
1519
1520        Note that variables must not appear more than once in the list of hints.
1521        """
1522        self.__helper.add_hint(var.index, value)
1523
1524    # Input/Output
1525    def export_to_lp_string(self, obfuscate: bool = False) -> str:
1526        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1527        options.obfuscate = obfuscate
1528        return self.__helper.export_to_lp_string(options)
1529
1530    def export_to_mps_string(self, obfuscate: bool = False) -> str:
1531        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1532        options.obfuscate = obfuscate
1533        return self.__helper.export_to_mps_string(options)
1534
1535    def export_to_proto(self) -> linear_solver_pb2.MPModelProto:
1536        """Exports the optimization model to a ProtoBuf format."""
1537        return mbh.to_mpmodel_proto(self.__helper)
1538
1539    def import_from_mps_string(self, mps_string: str) -> bool:
1540        """Reads a model from a MPS string."""
1541        return self.__helper.import_from_mps_string(mps_string)
1542
1543    def import_from_mps_file(self, mps_file: str) -> bool:
1544        """Reads a model from a .mps file."""
1545        return self.__helper.import_from_mps_file(mps_file)
1546
1547    def import_from_lp_string(self, lp_string: str) -> bool:
1548        """Reads a model from a LP string."""
1549        return self.__helper.import_from_lp_string(lp_string)
1550
1551    def import_from_lp_file(self, lp_file: str) -> bool:
1552        """Reads a model from a .lp file."""
1553        return self.__helper.import_from_lp_file(lp_file)
1554
1555    def import_from_proto_file(self, proto_file: str) -> bool:
1556        """Reads a model from a proto file."""
1557        return self.__helper.read_model_from_proto_file(proto_file)
1558
1559    def export_to_proto_file(self, proto_file: str) -> bool:
1560        """Writes a model to a proto file."""
1561        return self.__helper.write_model_to_proto_file(proto_file)
1562
1563    # Model getters and Setters
1564
1565    @property
1566    def num_variables(self) -> int:
1567        """Returns the number of variables in the model."""
1568        return self.__helper.num_variables()
1569
1570    @property
1571    def num_constraints(self) -> int:
1572        """The number of constraints in the model."""
1573        return self.__helper.num_constraints()
1574
1575    @property
1576    def name(self) -> str:
1577        """The name of the model."""
1578        return self.__helper.name()
1579
1580    @name.setter
1581    def name(self, name: str):
1582        self.__helper.set_name(name)
1583
1584    @property
1585    def helper(self) -> mbh.ModelBuilderHelper:
1586        """Returns the model builder helper."""
1587        return self.__helper
1588
1589
1590class Solver:
1591    """Main solver class.
1592
1593    The purpose of this class is to search for a solution to the model provided
1594    to the solve() method.
1595
1596    Once solve() is called, this class allows inspecting the solution found
1597    with the value() method, as well as general statistics about the solve
1598    procedure.
1599    """
1600
1601    def __init__(self, solver_name: str):
1602        self.__solve_helper: mbh.ModelSolverHelper = mbh.ModelSolverHelper(solver_name)
1603        self.log_callback: Optional[Callable[[str], None]] = None
1604
1605    def solver_is_supported(self) -> bool:
1606        """Checks whether the requested solver backend was found."""
1607        return self.__solve_helper.solver_is_supported()
1608
1609    # Solver backend and parameters.
1610    def set_time_limit_in_seconds(self, limit: NumberT) -> None:
1611        """Sets a time limit for the solve() call."""
1612        self.__solve_helper.set_time_limit_in_seconds(limit)
1613
1614    def set_solver_specific_parameters(self, parameters: str) -> None:
1615        """Sets parameters specific to the solver backend."""
1616        self.__solve_helper.set_solver_specific_parameters(parameters)
1617
1618    def enable_output(self, enabled: bool) -> None:
1619        """Controls the solver backend logs."""
1620        self.__solve_helper.enable_output(enabled)
1621
1622    def solve(self, model: Model) -> SolveStatus:
1623        """Solves a problem and passes each solution to the callback if not null."""
1624        if self.log_callback is not None:
1625            self.__solve_helper.set_log_callback(self.log_callback)
1626        else:
1627            self.__solve_helper.clear_log_callback()
1628        self.__solve_helper.solve(model.helper)
1629        return SolveStatus(self.__solve_helper.status())
1630
1631    def stop_search(self):
1632        """Stops the current search asynchronously."""
1633        self.__solve_helper.interrupt_solve()
1634
1635    def value(self, expr: LinearExprT) -> np.double:
1636        """Returns the value of a linear expression after solve."""
1637        if not self.__solve_helper.has_solution():
1638            return pd.NA
1639        if mbn.is_a_number(expr):
1640            return expr
1641        elif isinstance(expr, Variable):
1642            return self.__solve_helper.var_value(expr.index)
1643        elif isinstance(expr, LinearExpr):
1644            flat_expr = _as_flat_linear_expression(expr)
1645            return self.__solve_helper.expression_value(
1646                flat_expr._variable_indices,
1647                flat_expr._coefficients,
1648                flat_expr._offset,
1649            )
1650        else:
1651            raise TypeError(f"Unknown expression {expr!r} of type {type(expr)}")
1652
1653    def values(self, variables: _IndexOrSeries) -> pd.Series:
1654        """Returns the values of the input variables.
1655
1656        If `variables` is a `pd.Index`, then the output will be indexed by the
1657        variables. If `variables` is a `pd.Series` indexed by the underlying
1658        dimensions, then the output will be indexed by the same underlying
1659        dimensions.
1660
1661        Args:
1662          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1663            get the values.
1664
1665        Returns:
1666          pd.Series: The values of all variables in the set.
1667        """
1668        if not self.__solve_helper.has_solution():
1669            return _attribute_series(func=lambda v: pd.NA, values=variables)
1670        return _attribute_series(
1671            func=lambda v: self.__solve_helper.var_value(v.index),
1672            values=variables,
1673        )
1674
1675    def reduced_costs(self, variables: _IndexOrSeries) -> pd.Series:
1676        """Returns the reduced cost of the input variables.
1677
1678        If `variables` is a `pd.Index`, then the output will be indexed by the
1679        variables. If `variables` is a `pd.Series` indexed by the underlying
1680        dimensions, then the output will be indexed by the same underlying
1681        dimensions.
1682
1683        Args:
1684          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1685            get the values.
1686
1687        Returns:
1688          pd.Series: The reduced cost of all variables in the set.
1689        """
1690        if not self.__solve_helper.has_solution():
1691            return _attribute_series(func=lambda v: pd.NA, values=variables)
1692        return _attribute_series(
1693            func=lambda v: self.__solve_helper.reduced_cost(v.index),
1694            values=variables,
1695        )
1696
1697    def reduced_cost(self, var: Variable) -> np.double:
1698        """Returns the reduced cost of a linear expression after solve."""
1699        if not self.__solve_helper.has_solution():
1700            return pd.NA
1701        return self.__solve_helper.reduced_cost(var.index)
1702
1703    def dual_values(self, constraints: _IndexOrSeries) -> pd.Series:
1704        """Returns the dual values of the input constraints.
1705
1706        If `constraints` is a `pd.Index`, then the output will be indexed by the
1707        constraints. If `constraints` is a `pd.Series` indexed by the underlying
1708        dimensions, then the output will be indexed by the same underlying
1709        dimensions.
1710
1711        Args:
1712          constraints (Union[pd.Index, pd.Series]): The set of constraints from
1713            which to get the dual values.
1714
1715        Returns:
1716          pd.Series: The dual_values of all constraints in the set.
1717        """
1718        if not self.__solve_helper.has_solution():
1719            return _attribute_series(func=lambda v: pd.NA, values=constraints)
1720        return _attribute_series(
1721            func=lambda v: self.__solve_helper.dual_value(v.index),
1722            values=constraints,
1723        )
1724
1725    def dual_value(self, ct: LinearConstraint) -> np.double:
1726        """Returns the dual value of a linear constraint after solve."""
1727        if not self.__solve_helper.has_solution():
1728            return pd.NA
1729        return self.__solve_helper.dual_value(ct.index)
1730
1731    def activity(self, ct: LinearConstraint) -> np.double:
1732        """Returns the activity of a linear constraint after solve."""
1733        if not self.__solve_helper.has_solution():
1734            return pd.NA
1735        return self.__solve_helper.activity(ct.index)
1736
1737    @property
1738    def objective_value(self) -> np.double:
1739        """Returns the value of the objective after solve."""
1740        if not self.__solve_helper.has_solution():
1741            return pd.NA
1742        return self.__solve_helper.objective_value()
1743
1744    @property
1745    def best_objective_bound(self) -> np.double:
1746        """Returns the best lower (upper) bound found when min(max)imizing."""
1747        if not self.__solve_helper.has_solution():
1748            return pd.NA
1749        return self.__solve_helper.best_objective_bound()
1750
1751    @property
1752    def status_string(self) -> str:
1753        """Returns additional information of the last solve.
1754
1755        It can describe why the model is invalid.
1756        """
1757        return self.__solve_helper.status_string()
1758
1759    @property
1760    def wall_time(self) -> np.double:
1761        return self.__solve_helper.wall_time()
1762
1763    @property
1764    def user_time(self) -> np.double:
1765        return self.__solve_helper.user_time()
1766
1767
1768# The maximum number of terms to display in a linear expression's repr.
1769_MAX_LINEAR_EXPRESSION_REPR_TERMS = 5
1770
1771
1772@dataclasses.dataclass(repr=False, eq=False, frozen=True)
1773class _LinearExpression(LinearExpr):
1774    """For variables x, an expression: offset + sum_{i in I} coeff_i * x_i."""
1775
1776    __slots__ = ("_variable_indices", "_coefficients", "_offset", "_helper")
1777
1778    _variable_indices: npt.NDArray[np.int32]
1779    _coefficients: npt.NDArray[np.double]
1780    _offset: float
1781    _helper: Optional[mbh.ModelBuilderHelper]
1782
1783    @property
1784    def variable_indices(self) -> npt.NDArray[np.int32]:
1785        return self._variable_indices
1786
1787    @property
1788    def coefficients(self) -> npt.NDArray[np.double]:
1789        return self._coefficients
1790
1791    @property
1792    def constant(self) -> float:
1793        return self._offset
1794
1795    @property
1796    def helper(self) -> Optional[mbh.ModelBuilderHelper]:
1797        return self._helper
1798
1799    def __repr__(self):
1800        return self.__str__()
1801
1802    def __str__(self):
1803        if self._helper is None:
1804            return str(self._offset)
1805
1806        result = []
1807        for index, coeff in zip(self.variable_indices, self.coefficients):
1808            if len(result) >= _MAX_LINEAR_EXPRESSION_REPR_TERMS:
1809                result.append(" + ...")
1810                break
1811            var_name = Variable(self._helper, index, None, None, None).name
1812            if not result and mbn.is_one(coeff):
1813                result.append(var_name)
1814            elif not result and mbn.is_minus_one(coeff):
1815                result.append(f"-{var_name}")
1816            elif not result:
1817                result.append(f"{coeff} * {var_name}")
1818            elif mbn.is_one(coeff):
1819                result.append(f" + {var_name}")
1820            elif mbn.is_minus_one(coeff):
1821                result.append(f" - {var_name}")
1822            elif coeff > 0.0:
1823                result.append(f" + {coeff} * {var_name}")
1824            elif coeff < 0.0:
1825                result.append(f" - {-coeff} * {var_name}")
1826
1827        if not result:
1828            return f"{self.constant}"
1829        if self.constant > 0:
1830            result.append(f" + {self.constant}")
1831        elif self.constant < 0:
1832            result.append(f" - {-self.constant}")
1833        return "".join(result)
1834
1835
1836def _sum_as_flat_linear_expression(
1837    to_process: List[Tuple[LinearExprT, float]], offset: float = 0.0
1838) -> _LinearExpression:
1839    """Creates a _LinearExpression as the sum of terms."""
1840    indices = []
1841    coeffs = []
1842    helper = None
1843    while to_process:  # Flatten AST of LinearTypes.
1844        expr, coeff = to_process.pop()
1845        if isinstance(expr, _Sum):
1846            to_process.append((expr._left, coeff))
1847            to_process.append((expr._right, coeff))
1848        elif isinstance(expr, Variable):
1849            indices.append([expr.index])
1850            coeffs.append([coeff])
1851            if helper is None:
1852                helper = expr.helper
1853        elif mbn.is_a_number(expr):
1854            offset += coeff * cast(NumberT, expr)
1855        elif isinstance(expr, _Product):
1856            to_process.append((expr._expression, coeff * expr._coefficient))
1857        elif isinstance(expr, _LinearExpression):
1858            offset += coeff * expr._offset
1859            if expr._helper is not None:
1860                indices.append(expr.variable_indices)
1861                coeffs.append(np.multiply(expr.coefficients, coeff))
1862                if helper is None:
1863                    helper = expr._helper
1864        else:
1865            raise TypeError(
1866                "Unrecognized linear expression: " + str(expr) + f" {type(expr)}"
1867            )
1868
1869    if helper is not None:
1870        all_indices: npt.NDArray[np.int32] = np.concatenate(indices, axis=0)
1871        all_coeffs: npt.NDArray[np.double] = np.concatenate(coeffs, axis=0)
1872        sorted_indices, sorted_coefficients = helper.sort_and_regroup_terms(
1873            all_indices, all_coeffs
1874        )
1875        return _LinearExpression(sorted_indices, sorted_coefficients, offset, helper)
1876    else:
1877        assert not indices
1878        assert not coeffs
1879        return _LinearExpression(
1880            _variable_indices=np.zeros(dtype=np.int32, shape=[0]),
1881            _coefficients=np.zeros(dtype=np.double, shape=[0]),
1882            _offset=offset,
1883            _helper=None,
1884        )
1885
1886
1887def _as_flat_linear_expression(base_expr: LinearExprT) -> _LinearExpression:
1888    """Converts floats, ints and Linear objects to a LinearExpression."""
1889    if isinstance(base_expr, _LinearExpression):
1890        return base_expr
1891    return _sum_as_flat_linear_expression(to_process=[(base_expr, 1.0)], offset=0.0)
1892
1893
1894@dataclasses.dataclass(repr=False, eq=False, frozen=True)
1895class _Sum(LinearExpr):
1896    """Represents the (deferred) sum of two expressions."""
1897
1898    __slots__ = ("_left", "_right")
1899
1900    _left: LinearExprT
1901    _right: LinearExprT
1902
1903    def __repr__(self):
1904        return self.__str__()
1905
1906    def __str__(self):
1907        return str(_as_flat_linear_expression(self))
1908
1909
1910@dataclasses.dataclass(repr=False, eq=False, frozen=True)
1911class _Product(LinearExpr):
1912    """Represents the (deferred) product of an expression by a constant."""
1913
1914    __slots__ = ("_expression", "_coefficient")
1915
1916    _expression: LinearExpr
1917    _coefficient: NumberT
1918
1919    def __repr__(self):
1920        return self.__str__()
1921
1922    def __str__(self):
1923        return str(_as_flat_linear_expression(self))
1924
1925
1926def _get_index(obj: _IndexOrSeries) -> pd.Index:
1927    """Returns the indices of `obj` as a `pd.Index`."""
1928    if isinstance(obj, pd.Series):
1929        return obj.index
1930    return obj
1931
1932
1933def _attribute_series(
1934    *,
1935    func: Callable[[_VariableOrConstraint], NumberT],
1936    values: _IndexOrSeries,
1937) -> pd.Series:
1938    """Returns the attributes of `values`.
1939
1940    Args:
1941      func: The function to call for getting the attribute data.
1942      values: The values that the function will be applied (element-wise) to.
1943
1944    Returns:
1945      pd.Series: The attribute values.
1946    """
1947    return pd.Series(
1948        data=[func(v) for v in values],
1949        index=_get_index(values),
1950    )
1951
1952
1953def _convert_to_series_and_validate_index(
1954    value_or_series: Union[bool, NumberT, pd.Series], index: pd.Index
1955) -> pd.Series:
1956    """Returns a pd.Series of the given index with the corresponding values.
1957
1958    Args:
1959      value_or_series: the values to be converted (if applicable).
1960      index: the index of the resulting pd.Series.
1961
1962    Returns:
1963      pd.Series: The set of values with the given index.
1964
1965    Raises:
1966      TypeError: If the type of `value_or_series` is not recognized.
1967      ValueError: If the index does not match.
1968    """
1969    if mbn.is_a_number(value_or_series) or isinstance(value_or_series, bool):
1970        result = pd.Series(data=value_or_series, index=index)
1971    elif isinstance(value_or_series, pd.Series):
1972        if value_or_series.index.equals(index):
1973            result = value_or_series
1974        else:
1975            raise ValueError("index does not match")
1976    else:
1977        raise TypeError("invalid type={}".format(type(value_or_series)))
1978    return result
1979
1980
1981def _convert_to_var_series_and_validate_index(
1982    var_or_series: Union["Variable", pd.Series], index: pd.Index
1983) -> pd.Series:
1984    """Returns a pd.Series of the given index with the corresponding values.
1985
1986    Args:
1987      var_or_series: the variables to be converted (if applicable).
1988      index: the index of the resulting pd.Series.
1989
1990    Returns:
1991      pd.Series: The set of values with the given index.
1992
1993    Raises:
1994      TypeError: If the type of `value_or_series` is not recognized.
1995      ValueError: If the index does not match.
1996    """
1997    if isinstance(var_or_series, Variable):
1998        result = pd.Series(data=var_or_series, index=index)
1999    elif isinstance(var_or_series, pd.Series):
2000        if var_or_series.index.equals(index):
2001            result = var_or_series
2002        else:
2003            raise ValueError("index does not match")
2004    else:
2005        raise TypeError("invalid type={}".format(type(var_or_series)))
2006    return result
2007
2008
2009# Compatibility.
2010ModelBuilder = Model
2011ModelSolver = Solver
NumberT = typing.Union[int, float, numbers.Real, numpy.number]
IntegerT = typing.Union[int, numbers.Integral, numpy.integer]
LinearExprT = typing.Union[ForwardRef('LinearExpr'), int, float, numbers.Real, numpy.number]
ConstraintT = typing.Union[ForwardRef('_BoundedLinearExpr'), bool]
class SolveStatus(pybind11_builtins.pybind11_object):

Members:

OPTIMAL

FEASIBLE

INFEASIBLE

UNBOUNDED

ABNORMAL

NOT_SOLVED

MODEL_IS_VALID

CANCELLED_BY_USER

UNKNOWN_STATUS

MODEL_INVALID

INVALID_SOLVER_PARAMETERS

SOLVER_TYPE_UNAVAILABLE

INCOMPATIBLE_OPTIONS

SolveStatus()

__init__(self: SolveStatus, value: int) -> None

name

name(self: handle) -> str

value

(arg0: SolveStatus) -> int

OPTIMAL = <SolveStatus.OPTIMAL: 0>
FEASIBLE = <SolveStatus.FEASIBLE: 1>
INFEASIBLE = <SolveStatus.INFEASIBLE: 2>
UNBOUNDED = <SolveStatus.UNBOUNDED: 3>
ABNORMAL = <SolveStatus.ABNORMAL: 4>
NOT_SOLVED = <SolveStatus.NOT_SOLVED: 5>
MODEL_IS_VALID = <SolveStatus.MODEL_IS_VALID: 6>
CANCELLED_BY_USER = <SolveStatus.CANCELLED_BY_USER: 7>
UNKNOWN_STATUS = <SolveStatus.UNKNOWN_STATUS: 8>
MODEL_INVALID = <SolveStatus.MODEL_INVALID: 9>
INVALID_SOLVER_PARAMETERS = <SolveStatus.INVALID_SOLVER_PARAMETERS: 10>
SOLVER_TYPE_UNAVAILABLE = <SolveStatus.SOLVER_TYPE_UNAVAILABLE: 11>
INCOMPATIBLE_OPTIONS = <SolveStatus.INCOMPATIBLE_OPTIONS: 12>
class LinearExpr:
 66class LinearExpr(metaclass=abc.ABCMeta):
 67    """Holds an linear expression.
 68
 69    A linear expression is built from constants and variables.
 70    For example, `x + 2.0 * (y - z + 1.0)`.
 71
 72    Linear expressions are used in Model models in constraints and in the
 73    objective:
 74
 75    * You can define linear constraints as in:
 76
 77    ```
 78    model.add(x + 2 * y <= 5.0)
 79    model.add(sum(array_of_vars) == 5.0)
 80    ```
 81
 82    * In Model, the objective is a linear expression:
 83
 84    ```
 85    model.minimize(x + 2.0 * y + z)
 86    ```
 87
 88    * For large arrays, using the LinearExpr class is faster that using the python
 89    `sum()` function. You can create constraints and the objective from lists of
 90    linear expressions or coefficients as follows:
 91
 92    ```
 93    model.minimize(model_builder.LinearExpr.sum(expressions))
 94    model.add(model_builder.LinearExpr.weighted_sum(expressions, coeffs) >= 0)
 95    ```
 96    """
 97
 98    @classmethod
 99    def sum(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
100        cls, expressions: Sequence[LinearExprT], *, constant: NumberT = 0.0
101    ) -> LinearExprT:
102        """Creates `sum(expressions) + constant`.
103
104        It can perform simple simplifications and returns different objects,
105        including the input.
106
107        Args:
108          expressions: a sequence of linear expressions or constants.
109          constant: a numerical constant.
110
111        Returns:
112          a LinearExpr instance or a numerical constant.
113        """
114        checked_constant: np.double = mbn.assert_is_a_number(constant)
115        if not expressions:
116            return checked_constant
117        if len(expressions) == 1 and mbn.is_zero(checked_constant):
118            return expressions[0]
119
120        return LinearExpr.weighted_sum(
121            expressions, np.ones(len(expressions)), constant=checked_constant
122        )
123
124    @classmethod
125    def weighted_sum(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
126        cls,
127        expressions: Sequence[LinearExprT],
128        coefficients: Sequence[NumberT],
129        *,
130        constant: NumberT = 0.0,
131    ) -> Union[NumberT, "_LinearExpression"]:
132        """Creates `sum(expressions[i] * coefficients[i]) + constant`.
133
134        It can perform simple simplifications and returns different object,
135        including the input.
136
137        Args:
138          expressions: a sequence of linear expressions or constants.
139          coefficients: a sequence of numerical constants.
140          constant: a numerical constant.
141
142        Returns:
143          a _LinearExpression instance or a numerical constant.
144        """
145        if len(expressions) != len(coefficients):
146            raise ValueError(
147                "LinearExpr.weighted_sum: expressions and coefficients have"
148                " different lengths"
149            )
150        checked_constant: np.double = mbn.assert_is_a_number(constant)
151        if not expressions:
152            return checked_constant
153        return _sum_as_flat_linear_expression(
154            to_process=list(zip(expressions, coefficients)), offset=checked_constant
155        )
156
157    @classmethod
158    def term(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
159        cls,
160        expression: LinearExprT,
161        coefficient: NumberT,
162        *,
163        constant: NumberT = 0.0,
164    ) -> LinearExprT:
165        """Creates `expression * coefficient + constant`.
166
167        It can perform simple simplifications and returns different object,
168        including the input.
169        Args:
170          expression: a linear expression or a constant.
171          coefficient: a numerical constant.
172          constant: a numerical constant.
173
174        Returns:
175          a LinearExpr instance or a numerical constant.
176        """
177        checked_coefficient: np.double = mbn.assert_is_a_number(coefficient)
178        checked_constant: np.double = mbn.assert_is_a_number(constant)
179
180        if mbn.is_zero(checked_coefficient):
181            return checked_constant
182        if mbn.is_one(checked_coefficient) and mbn.is_zero(checked_constant):
183            return expression
184        if mbn.is_a_number(expression):
185            return np.double(expression) * checked_coefficient + checked_constant
186        if isinstance(expression, LinearExpr):
187            return _as_flat_linear_expression(
188                expression * checked_coefficient + checked_constant
189            )
190        raise TypeError(f"Unknown expression {expression!r} of type {type(expression)}")
191
192    def __hash__(self):
193        return object.__hash__(self)
194
195    def __add__(self, arg: LinearExprT) -> "_Sum":
196        return _Sum(self, arg)
197
198    def __radd__(self, arg: LinearExprT) -> "_Sum":
199        return self.__add__(arg)
200
201    def __sub__(self, arg: LinearExprT) -> "_Sum":
202        return _Sum(self, -arg)
203
204    def __rsub__(self, arg: LinearExprT) -> "_Sum":
205        return _Sum(-self, arg)
206
207    def __mul__(self, arg: NumberT) -> "_Product":
208        return _Product(self, arg)
209
210    def __rmul__(self, arg: NumberT) -> "_Product":
211        return self.__mul__(arg)
212
213    def __truediv__(self, coeff: NumberT) -> "_Product":
214        return self.__mul__(1.0 / coeff)
215
216    def __neg__(self) -> "_Product":
217        return _Product(self, -1)
218
219    def __bool__(self):
220        raise NotImplementedError(f"Cannot use a LinearExpr {self} as a Boolean value")
221
222    def __eq__(self, arg: LinearExprT) -> "BoundedLinearExpression":
223        return BoundedLinearExpression(self - arg, 0, 0)
224
225    def __ge__(self, arg: LinearExprT) -> "BoundedLinearExpression":
226        return BoundedLinearExpression(
227            self - arg, 0, math.inf
228        )  # pytype: disable=wrong-arg-types  # numpy-scalars
229
230    def __le__(self, arg: LinearExprT) -> "BoundedLinearExpression":
231        return BoundedLinearExpression(
232            self - arg, -math.inf, 0
233        )  # pytype: disable=wrong-arg-types  # numpy-scalars

Holds an linear expression.

A linear expression is built from constants and variables. For example, x + 2.0 * (y - z + 1.0).

Linear expressions are used in Model models in constraints and in the objective:

  • You can define linear constraints as in:
model.add(x + 2 * y <= 5.0)
model.add(sum(array_of_vars) == 5.0)
  • In Model, the objective is a linear expression:
model.minimize(x + 2.0 * 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(model_builder.LinearExpr.sum(expressions))
model.add(model_builder.LinearExpr.weighted_sum(expressions, coeffs) >= 0)
@classmethod
def sum( cls, expressions: Sequence[Union[LinearExpr, int, float, numbers.Real, numpy.number]], *, constant: Union[int, float, numbers.Real, numpy.number] = 0.0) -> Union[LinearExpr, int, float, numbers.Real, numpy.number]:
 98    @classmethod
 99    def sum(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
100        cls, expressions: Sequence[LinearExprT], *, constant: NumberT = 0.0
101    ) -> LinearExprT:
102        """Creates `sum(expressions) + constant`.
103
104        It can perform simple simplifications and returns different objects,
105        including the input.
106
107        Args:
108          expressions: a sequence of linear expressions or constants.
109          constant: a numerical constant.
110
111        Returns:
112          a LinearExpr instance or a numerical constant.
113        """
114        checked_constant: np.double = mbn.assert_is_a_number(constant)
115        if not expressions:
116            return checked_constant
117        if len(expressions) == 1 and mbn.is_zero(checked_constant):
118            return expressions[0]
119
120        return LinearExpr.weighted_sum(
121            expressions, np.ones(len(expressions)), constant=checked_constant
122        )

Creates sum(expressions) + constant.

It can perform simple simplifications and returns different objects, including the input.

Arguments:
  • expressions: a sequence of linear expressions or constants.
  • constant: a numerical constant.
Returns:

a LinearExpr instance or a numerical constant.

@classmethod
def weighted_sum( cls, expressions: Sequence[Union[LinearExpr, int, float, numbers.Real, numpy.number]], coefficients: Sequence[Union[int, float, numbers.Real, numpy.number]], *, constant: Union[int, float, numbers.Real, numpy.number] = 0.0) -> Union[int, float, numbers.Real, numpy.number, ortools.linear_solver.python.model_builder._LinearExpression]:
124    @classmethod
125    def weighted_sum(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
126        cls,
127        expressions: Sequence[LinearExprT],
128        coefficients: Sequence[NumberT],
129        *,
130        constant: NumberT = 0.0,
131    ) -> Union[NumberT, "_LinearExpression"]:
132        """Creates `sum(expressions[i] * coefficients[i]) + constant`.
133
134        It can perform simple simplifications and returns different object,
135        including the input.
136
137        Args:
138          expressions: a sequence of linear expressions or constants.
139          coefficients: a sequence of numerical constants.
140          constant: a numerical constant.
141
142        Returns:
143          a _LinearExpression instance or a numerical constant.
144        """
145        if len(expressions) != len(coefficients):
146            raise ValueError(
147                "LinearExpr.weighted_sum: expressions and coefficients have"
148                " different lengths"
149            )
150        checked_constant: np.double = mbn.assert_is_a_number(constant)
151        if not expressions:
152            return checked_constant
153        return _sum_as_flat_linear_expression(
154            to_process=list(zip(expressions, coefficients)), offset=checked_constant
155        )

Creates sum(expressions[i] * coefficients[i]) + constant.

It can perform simple simplifications and returns different object, including the input.

Arguments:
  • expressions: a sequence of linear expressions or constants.
  • coefficients: a sequence of numerical constants.
  • constant: a numerical constant.
Returns:

a _LinearExpression instance or a numerical constant.

@classmethod
def term( cls, expression: Union[LinearExpr, int, float, numbers.Real, numpy.number], coefficient: Union[int, float, numbers.Real, numpy.number], *, constant: Union[int, float, numbers.Real, numpy.number] = 0.0) -> Union[LinearExpr, int, float, numbers.Real, numpy.number]:
157    @classmethod
158    def term(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
159        cls,
160        expression: LinearExprT,
161        coefficient: NumberT,
162        *,
163        constant: NumberT = 0.0,
164    ) -> LinearExprT:
165        """Creates `expression * coefficient + constant`.
166
167        It can perform simple simplifications and returns different object,
168        including the input.
169        Args:
170          expression: a linear expression or a constant.
171          coefficient: a numerical constant.
172          constant: a numerical constant.
173
174        Returns:
175          a LinearExpr instance or a numerical constant.
176        """
177        checked_coefficient: np.double = mbn.assert_is_a_number(coefficient)
178        checked_constant: np.double = mbn.assert_is_a_number(constant)
179
180        if mbn.is_zero(checked_coefficient):
181            return checked_constant
182        if mbn.is_one(checked_coefficient) and mbn.is_zero(checked_constant):
183            return expression
184        if mbn.is_a_number(expression):
185            return np.double(expression) * checked_coefficient + checked_constant
186        if isinstance(expression, LinearExpr):
187            return _as_flat_linear_expression(
188                expression * checked_coefficient + checked_constant
189            )
190        raise TypeError(f"Unknown expression {expression!r} of type {type(expression)}")

Creates expression * coefficient + constant.

It can perform simple simplifications and returns different object, including the input.

Arguments:
  • expression: a linear expression or a constant.
  • coefficient: a numerical constant.
  • constant: a numerical constant.
Returns:

a LinearExpr instance or a numerical constant.

class Variable(LinearExpr):
236class Variable(LinearExpr):
237    """A variable (continuous or integral).
238
239    A Variable is an object that can take on any integer value within defined
240    ranges. Variables appear in constraint like:
241
242        x + y >= 5
243
244    Solving a model is equivalent to finding, for each variable, a single value
245    from the set of initial values (called the initial domain), such that the
246    model is feasible, or optimal if you provided an objective function.
247    """
248
249    def __init__(
250        self,
251        helper: mbh.ModelBuilderHelper,
252        lb: NumberT,
253        ub: Optional[NumberT],
254        is_integral: Optional[bool],
255        name: Optional[str],
256    ):
257        """See Model.new_var below."""
258        LinearExpr.__init__(self)
259        self.__helper: mbh.ModelBuilderHelper = helper
260        # Python do not support multiple __init__ methods.
261        # This method is only called from the Model class.
262        # We hack the parameter to support the two cases:
263        # case 1:
264        #     helper is a ModelBuilderHelper, lb is a double value, ub is a double
265        #     value, is_integral is a Boolean value, and name is a string.
266        # case 2:
267        #     helper is a ModelBuilderHelper, lb is an index (int), ub is None,
268        #     is_integral is None, and name is None.
269        if mbn.is_integral(lb) and ub is None and is_integral is None:
270            self.__index: np.int32 = np.int32(lb)
271            self.__helper: mbh.ModelBuilderHelper = helper
272        else:
273            index: np.int32 = helper.add_var()
274            self.__index: np.int32 = np.int32(index)
275            self.__helper: mbh.ModelBuilderHelper = helper
276            helper.set_var_lower_bound(index, lb)
277            helper.set_var_upper_bound(index, ub)
278            helper.set_var_integrality(index, is_integral)
279            if name:
280                helper.set_var_name(index, name)
281
282    @property
283    def index(self) -> np.int32:
284        """Returns the index of the variable in the helper."""
285        return self.__index
286
287    @property
288    def helper(self) -> mbh.ModelBuilderHelper:
289        """Returns the underlying ModelBuilderHelper."""
290        return self.__helper
291
292    def is_equal_to(self, other: LinearExprT) -> bool:
293        """Returns true if self == other in the python sense."""
294        if not isinstance(other, Variable):
295            return False
296        return self.index == other.index and self.helper == other.helper
297
298    def __str__(self) -> str:
299        return self.name
300
301    def __repr__(self) -> str:
302        return self.__str__()
303
304    @property
305    def name(self) -> str:
306        """Returns the name of the variable."""
307        var_name = self.__helper.var_name(self.__index)
308        if var_name:
309            return var_name
310        return f"variable#{self.index}"
311
312    @name.setter
313    def name(self, name: str) -> None:
314        """Sets the name of the variable."""
315        self.__helper.set_var_name(self.__index, name)
316
317    @property
318    def lower_bound(self) -> np.double:
319        """Returns the lower bound of the variable."""
320        return self.__helper.var_lower_bound(self.__index)
321
322    @lower_bound.setter
323    def lower_bound(self, bound: NumberT) -> None:
324        """Sets the lower bound of the variable."""
325        self.__helper.set_var_lower_bound(self.__index, bound)
326
327    @property
328    def upper_bound(self) -> np.double:
329        """Returns the upper bound of the variable."""
330        return self.__helper.var_upper_bound(self.__index)
331
332    @upper_bound.setter
333    def upper_bound(self, bound: NumberT) -> None:
334        """Sets the upper bound of the variable."""
335        self.__helper.set_var_upper_bound(self.__index, bound)
336
337    @property
338    def is_integral(self) -> bool:
339        """Returns whether the variable is integral."""
340        return self.__helper.var_is_integral(self.__index)
341
342    @is_integral.setter
343    def integrality(self, is_integral: bool) -> None:
344        """Sets the integrality of the variable."""
345        self.__helper.set_var_integrality(self.__index, is_integral)
346
347    @property
348    def objective_coefficient(self) -> NumberT:
349        return self.__helper.var_objective_coefficient(self.__index)
350
351    @objective_coefficient.setter
352    def objective_coefficient(self, coeff: NumberT) -> None:
353        self.__helper.set_var_objective_coefficient(self.__index, coeff)
354
355    def __eq__(self, arg: Optional[LinearExprT]) -> ConstraintT:
356        if arg is None:
357            return False
358        if isinstance(arg, Variable):
359            return VarEqVar(self, arg)
360        return BoundedLinearExpression(
361            self - arg, 0.0, 0.0
362        )  # pytype: disable=wrong-arg-types  # numpy-scalars
363
364    def __hash__(self):
365        return hash((self.__helper, self.__index))

A variable (continuous or integral).

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

x + y >= 5

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

Variable( helper: ortools.linear_solver.python.model_builder_helper.ModelBuilderHelper, lb: Union[int, float, numbers.Real, numpy.number], ub: Union[int, float, numbers.Real, numpy.number, NoneType], is_integral: Optional[bool], name: Optional[str])
249    def __init__(
250        self,
251        helper: mbh.ModelBuilderHelper,
252        lb: NumberT,
253        ub: Optional[NumberT],
254        is_integral: Optional[bool],
255        name: Optional[str],
256    ):
257        """See Model.new_var below."""
258        LinearExpr.__init__(self)
259        self.__helper: mbh.ModelBuilderHelper = helper
260        # Python do not support multiple __init__ methods.
261        # This method is only called from the Model class.
262        # We hack the parameter to support the two cases:
263        # case 1:
264        #     helper is a ModelBuilderHelper, lb is a double value, ub is a double
265        #     value, is_integral is a Boolean value, and name is a string.
266        # case 2:
267        #     helper is a ModelBuilderHelper, lb is an index (int), ub is None,
268        #     is_integral is None, and name is None.
269        if mbn.is_integral(lb) and ub is None and is_integral is None:
270            self.__index: np.int32 = np.int32(lb)
271            self.__helper: mbh.ModelBuilderHelper = helper
272        else:
273            index: np.int32 = helper.add_var()
274            self.__index: np.int32 = np.int32(index)
275            self.__helper: mbh.ModelBuilderHelper = helper
276            helper.set_var_lower_bound(index, lb)
277            helper.set_var_upper_bound(index, ub)
278            helper.set_var_integrality(index, is_integral)
279            if name:
280                helper.set_var_name(index, name)

See Model.new_var below.

index: numpy.int32
282    @property
283    def index(self) -> np.int32:
284        """Returns the index of the variable in the helper."""
285        return self.__index

Returns the index of the variable in the helper.

287    @property
288    def helper(self) -> mbh.ModelBuilderHelper:
289        """Returns the underlying ModelBuilderHelper."""
290        return self.__helper

Returns the underlying ModelBuilderHelper.

def is_equal_to( self, other: Union[LinearExpr, int, float, numbers.Real, numpy.number]) -> bool:
292    def is_equal_to(self, other: LinearExprT) -> bool:
293        """Returns true if self == other in the python sense."""
294        if not isinstance(other, Variable):
295            return False
296        return self.index == other.index and self.helper == other.helper

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

name: str
304    @property
305    def name(self) -> str:
306        """Returns the name of the variable."""
307        var_name = self.__helper.var_name(self.__index)
308        if var_name:
309            return var_name
310        return f"variable#{self.index}"

Returns the name of the variable.

lower_bound: numpy.float64
317    @property
318    def lower_bound(self) -> np.double:
319        """Returns the lower bound of the variable."""
320        return self.__helper.var_lower_bound(self.__index)

Returns the lower bound of the variable.

upper_bound: numpy.float64
327    @property
328    def upper_bound(self) -> np.double:
329        """Returns the upper bound of the variable."""
330        return self.__helper.var_upper_bound(self.__index)

Returns the upper bound of the variable.

is_integral: bool
337    @property
338    def is_integral(self) -> bool:
339        """Returns whether the variable is integral."""
340        return self.__helper.var_is_integral(self.__index)

Returns whether the variable is integral.

integrality: bool
337    @property
338    def is_integral(self) -> bool:
339        """Returns whether the variable is integral."""
340        return self.__helper.var_is_integral(self.__index)

Returns whether the variable is integral.

objective_coefficient: Union[int, float, numbers.Real, numpy.number]
347    @property
348    def objective_coefficient(self) -> NumberT:
349        return self.__helper.var_objective_coefficient(self.__index)
Inherited Members
LinearExpr
sum
weighted_sum
term
@dataclasses.dataclass(repr=False, eq=False, frozen=True)
class VarEqVar(_BoundedLinearExpr):
501@dataclasses.dataclass(repr=False, eq=False, frozen=True)
502class VarEqVar(_BoundedLinearExpr):
503    """Represents var == var."""
504
505    __slots__ = ("left", "right")
506
507    left: Variable
508    right: Variable
509
510    def __str__(self):
511        return f"{self.left} == {self.right}"
512
513    def __repr__(self):
514        return self.__str__()
515
516    def __bool__(self) -> bool:
517        return hash(self.left) == hash(self.right)
518
519    def _add_linear_constraint(
520        self, helper: mbh.ModelBuilderHelper, name: str
521    ) -> "LinearConstraint":
522        c = LinearConstraint(helper)
523        helper.set_constraint_lower_bound(c.index, 0.0)
524        helper.set_constraint_upper_bound(c.index, 0.0)
525        # pylint: disable=protected-access
526        helper.add_term_to_constraint(c.index, self.left.index, 1.0)
527        helper.add_term_to_constraint(c.index, self.right.index, -1.0)
528        # pylint: enable=protected-access
529        helper.set_constraint_name(c.index, name)
530        return c
531
532    def _add_enforced_linear_constraint(
533        self,
534        helper: mbh.ModelBuilderHelper,
535        var: Variable,
536        value: bool,
537        name: str,
538    ) -> "EnforcedLinearConstraint":
539        """Adds an enforced linear constraint to the model."""
540        c = EnforcedLinearConstraint(helper)
541        c.indicator_variable = var
542        c.indicator_value = value
543        helper.set_enforced_constraint_lower_bound(c.index, 0.0)
544        helper.set_enforced_constraint_upper_bound(c.index, 0.0)
545        # pylint: disable=protected-access
546        helper.add_term_to_enforced_constraint(c.index, self.left.index, 1.0)
547        helper.add_term_to_enforced_constraint(c.index, self.right.index, -1.0)
548        # pylint: enable=protected-access
549        helper.set_enforced_constraint_name(c.index, name)
550        return c

Represents var == var.

VarEqVar( left: Variable, right: Variable)
left: Variable
right: Variable
class BoundedLinearExpression(_BoundedLinearExpr):
553class BoundedLinearExpression(_BoundedLinearExpr):
554    """Represents a linear constraint: `lb <= linear expression <= ub`.
555
556    The only use of this class is to be added to the Model through
557    `Model.add(bounded expression)`, as in:
558
559        model.Add(x + 2 * y -1 >= z)
560    """
561
562    def __init__(self, expr: LinearExprT, lb: NumberT, ub: NumberT):
563        self.__expr: LinearExprT = expr
564        self.__lb: np.double = mbn.assert_is_a_number(lb)
565        self.__ub: np.double = mbn.assert_is_a_number(ub)
566
567    def __str__(self) -> str:
568        if self.__lb > -math.inf and self.__ub < math.inf:
569            if self.__lb == self.__ub:
570                return f"{self.__expr} == {self.__lb}"
571            else:
572                return f"{self.__lb} <= {self.__expr} <= {self.__ub}"
573        elif self.__lb > -math.inf:
574            return f"{self.__expr} >= {self.__lb}"
575        elif self.__ub < math.inf:
576            return f"{self.__expr} <= {self.__ub}"
577        else:
578            return f"{self.__expr} free"
579
580    def __repr__(self):
581        return self.__str__()
582
583    @property
584    def expression(self) -> LinearExprT:
585        return self.__expr
586
587    @property
588    def lower_bound(self) -> np.double:
589        return self.__lb
590
591    @property
592    def upper_bound(self) -> np.double:
593        return self.__ub
594
595    def __bool__(self) -> bool:
596        raise NotImplementedError(
597            f"Cannot use a BoundedLinearExpression {self} as a Boolean value"
598        )
599
600    def _add_linear_constraint(
601        self, helper: mbh.ModelBuilderHelper, name: Optional[str]
602    ) -> "LinearConstraint":
603        c = LinearConstraint(helper)
604        flat_expr = _as_flat_linear_expression(self.__expr)
605        # pylint: disable=protected-access
606        helper.add_terms_to_constraint(
607            c.index, flat_expr._variable_indices, flat_expr._coefficients
608        )
609        helper.set_constraint_lower_bound(c.index, self.__lb - flat_expr._offset)
610        helper.set_constraint_upper_bound(c.index, self.__ub - flat_expr._offset)
611        # pylint: enable=protected-access
612        if name is not None:
613            helper.set_constraint_name(c.index, name)
614        return c
615
616    def _add_enforced_linear_constraint(
617        self,
618        helper: mbh.ModelBuilderHelper,
619        var: Variable,
620        value: bool,
621        name: Optional[str],
622    ) -> "EnforcedLinearConstraint":
623        """Adds an enforced linear constraint to the model."""
624        c = EnforcedLinearConstraint(helper)
625        c.indicator_variable = var
626        c.indicator_value = value
627        flat_expr = _as_flat_linear_expression(self.__expr)
628        # pylint: disable=protected-access
629        helper.add_terms_to_enforced_constraint(
630            c.index, flat_expr._variable_indices, flat_expr._coefficients
631        )
632        helper.set_enforced_constraint_lower_bound(
633            c.index, self.__lb - flat_expr._offset
634        )
635        helper.set_enforced_constraint_upper_bound(
636            c.index, self.__ub - flat_expr._offset
637        )
638        # pylint: enable=protected-access
639        if name is not None:
640            helper.set_enforced_constraint_name(c.index, name)
641        return c

Represents a linear constraint: lb <= linear expression <= ub.

The only use of this class is to be added to the Model through Model.add(bounded expression), as in:

model.Add(x + 2 * y -1 >= z)
BoundedLinearExpression( expr: Union[LinearExpr, int, float, numbers.Real, numpy.number], lb: Union[int, float, numbers.Real, numpy.number], ub: Union[int, float, numbers.Real, numpy.number])
562    def __init__(self, expr: LinearExprT, lb: NumberT, ub: NumberT):
563        self.__expr: LinearExprT = expr
564        self.__lb: np.double = mbn.assert_is_a_number(lb)
565        self.__ub: np.double = mbn.assert_is_a_number(ub)
expression: Union[LinearExpr, int, float, numbers.Real, numpy.number]
583    @property
584    def expression(self) -> LinearExprT:
585        return self.__expr
lower_bound: numpy.float64
587    @property
588    def lower_bound(self) -> np.double:
589        return self.__lb
upper_bound: numpy.float64
591    @property
592    def upper_bound(self) -> np.double:
593        return self.__ub
class LinearConstraint:
644class LinearConstraint:
645    """Stores a linear equation.
646
647    Example:
648        x = model.new_num_var(0, 10, 'x')
649        y = model.new_num_var(0, 10, 'y')
650
651        linear_constraint = model.add(x + 2 * y == 5)
652    """
653
654    def __init__(
655        self, helper: mbh.ModelBuilderHelper, index: Optional[IntegerT] = None
656    ):
657        if index is None:
658            self.__index = helper.add_linear_constraint()
659        else:
660            self.__index = index
661        self.__helper: mbh.ModelBuilderHelper = helper
662
663    @property
664    def index(self) -> IntegerT:
665        """Returns the index of the constraint in the helper."""
666        return self.__index
667
668    @property
669    def helper(self) -> mbh.ModelBuilderHelper:
670        """Returns the ModelBuilderHelper instance."""
671        return self.__helper
672
673    @property
674    def lower_bound(self) -> np.double:
675        return self.__helper.constraint_lower_bound(self.__index)
676
677    @lower_bound.setter
678    def lower_bound(self, bound: NumberT) -> None:
679        self.__helper.set_constraint_lower_bound(self.__index, bound)
680
681    @property
682    def upper_bound(self) -> np.double:
683        return self.__helper.constraint_upper_bound(self.__index)
684
685    @upper_bound.setter
686    def upper_bound(self, bound: NumberT) -> None:
687        self.__helper.set_constraint_upper_bound(self.__index, bound)
688
689    @property
690    def name(self) -> str:
691        constraint_name = self.__helper.constraint_name(self.__index)
692        if constraint_name:
693            return constraint_name
694        return f"linear_constraint#{self.__index}"
695
696    @name.setter
697    def name(self, name: str) -> None:
698        return self.__helper.set_constraint_name(self.__index, name)
699
700    def is_always_false(self) -> bool:
701        """Returns True if the constraint is always false.
702
703        Usually, it means that it was created by model.add(False)
704        """
705        return self.lower_bound > self.upper_bound
706
707    def __str__(self):
708        return self.name
709
710    def __repr__(self):
711        return (
712            f"LinearConstraint({self.name}, lb={self.lower_bound},"
713            f" ub={self.upper_bound},"
714            f" var_indices={self.helper.constraint_var_indices(self.index)},"
715            f" coefficients={self.helper.constraint_coefficients(self.index)})"
716        )
717
718    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
719        """Sets the coefficient of the variable in the constraint."""
720        if self.is_always_false():
721            raise ValueError(
722                f"Constraint {self.index} is always false and cannot be modified"
723            )
724        self.__helper.set_constraint_coefficient(self.__index, var.index, coeff)
725
726    def add_term(self, var: Variable, coeff: NumberT) -> None:
727        """Adds var * coeff to the constraint."""
728        if self.is_always_false():
729            raise ValueError(
730                f"Constraint {self.index} is always false and cannot be modified"
731            )
732        self.__helper.safe_add_term_to_constraint(self.__index, var.index, coeff)
733
734    def clear_terms(self) -> None:
735        """Clear all terms of the constraint."""
736        self.__helper.clear_constraint_terms(self.__index)

Stores a linear equation.

Example:

x = model.new_num_var(0, 10, 'x') y = model.new_num_var(0, 10, 'y')

linear_constraint = model.add(x + 2 * y == 5)

LinearConstraint( helper: ortools.linear_solver.python.model_builder_helper.ModelBuilderHelper, index: Union[int, numbers.Integral, numpy.integer, NoneType] = None)
654    def __init__(
655        self, helper: mbh.ModelBuilderHelper, index: Optional[IntegerT] = None
656    ):
657        if index is None:
658            self.__index = helper.add_linear_constraint()
659        else:
660            self.__index = index
661        self.__helper: mbh.ModelBuilderHelper = helper
index: Union[int, numbers.Integral, numpy.integer]
663    @property
664    def index(self) -> IntegerT:
665        """Returns the index of the constraint in the helper."""
666        return self.__index

Returns the index of the constraint in the helper.

668    @property
669    def helper(self) -> mbh.ModelBuilderHelper:
670        """Returns the ModelBuilderHelper instance."""
671        return self.__helper

Returns the ModelBuilderHelper instance.

lower_bound: numpy.float64
673    @property
674    def lower_bound(self) -> np.double:
675        return self.__helper.constraint_lower_bound(self.__index)
upper_bound: numpy.float64
681    @property
682    def upper_bound(self) -> np.double:
683        return self.__helper.constraint_upper_bound(self.__index)
name: str
689    @property
690    def name(self) -> str:
691        constraint_name = self.__helper.constraint_name(self.__index)
692        if constraint_name:
693            return constraint_name
694        return f"linear_constraint#{self.__index}"
def is_always_false(self) -> bool:
700    def is_always_false(self) -> bool:
701        """Returns True if the constraint is always false.
702
703        Usually, it means that it was created by model.add(False)
704        """
705        return self.lower_bound > self.upper_bound

Returns True if the constraint is always false.

Usually, it means that it was created by model.add(False)

def set_coefficient( self, var: Variable, coeff: Union[int, float, numbers.Real, numpy.number]) -> None:
718    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
719        """Sets the coefficient of the variable in the constraint."""
720        if self.is_always_false():
721            raise ValueError(
722                f"Constraint {self.index} is always false and cannot be modified"
723            )
724        self.__helper.set_constraint_coefficient(self.__index, var.index, coeff)

Sets the coefficient of the variable in the constraint.

def add_term( self, var: Variable, coeff: Union[int, float, numbers.Real, numpy.number]) -> None:
726    def add_term(self, var: Variable, coeff: NumberT) -> None:
727        """Adds var * coeff to the constraint."""
728        if self.is_always_false():
729            raise ValueError(
730                f"Constraint {self.index} is always false and cannot be modified"
731            )
732        self.__helper.safe_add_term_to_constraint(self.__index, var.index, coeff)

Adds var * coeff to the constraint.

def clear_terms(self) -> None:
734    def clear_terms(self) -> None:
735        """Clear all terms of the constraint."""
736        self.__helper.clear_constraint_terms(self.__index)

Clear all terms of the constraint.

class EnforcedLinearConstraint:
739class EnforcedLinearConstraint:
740    """Stores an enforced linear equation, also name indicator constraint.
741
742    Example:
743        x = model.new_num_var(0, 10, 'x')
744        y = model.new_num_var(0, 10, 'y')
745        z = model.new_bool_var('z')
746
747        enforced_linear_constraint = model.add_enforced(x + 2 * y == 5, z, False)
748    """
749
750    def __init__(
751        self, helper: mbh.ModelBuilderHelper, index: Optional[IntegerT] = None
752    ):
753        if index is None:
754            self.__index = helper.add_enforced_linear_constraint()
755        else:
756            if not helper.is_enforced_linear_constraint(index):
757                raise ValueError(
758                    f"the given index {index} does not refer to an enforced linear"
759                    " constraint"
760                )
761
762            self.__index = index
763        self.__helper: mbh.ModelBuilderHelper = helper
764
765    @property
766    def index(self) -> IntegerT:
767        """Returns the index of the constraint in the helper."""
768        return self.__index
769
770    @property
771    def helper(self) -> mbh.ModelBuilderHelper:
772        """Returns the ModelBuilderHelper instance."""
773        return self.__helper
774
775    @property
776    def lower_bound(self) -> np.double:
777        return self.__helper.enforced_constraint_lower_bound(self.__index)
778
779    @lower_bound.setter
780    def lower_bound(self, bound: NumberT) -> None:
781        self.__helper.set_enforced_constraint_lower_bound(self.__index, bound)
782
783    @property
784    def upper_bound(self) -> np.double:
785        return self.__helper.enforced_constraint_upper_bound(self.__index)
786
787    @upper_bound.setter
788    def upper_bound(self, bound: NumberT) -> None:
789        self.__helper.set_enforced_constraint_upper_bound(self.__index, bound)
790
791    @property
792    def indicator_variable(self) -> "Variable":
793        enforcement_var_index = (
794            self.__helper.enforced_constraint_indicator_variable_index(self.__index)
795        )
796        return Variable(self.__helper, enforcement_var_index, None, None, None)
797
798    @indicator_variable.setter
799    def indicator_variable(self, var: "Variable") -> None:
800        self.__helper.set_enforced_constraint_indicator_variable_index(
801            self.__index, var.index
802        )
803
804    @property
805    def indicator_value(self) -> bool:
806        return self.__helper.enforced_constraint_indicator_value(self.__index)
807
808    @indicator_value.setter
809    def indicator_value(self, value: bool) -> None:
810        self.__helper.set_enforced_constraint_indicator_value(self.__index, value)
811
812    @property
813    def name(self) -> str:
814        constraint_name = self.__helper.enforced_constraint_name(self.__index)
815        if constraint_name:
816            return constraint_name
817        return f"enforced_linear_constraint#{self.__index}"
818
819    @name.setter
820    def name(self, name: str) -> None:
821        return self.__helper.set_enforced_constraint_name(self.__index, name)
822
823    def is_always_false(self) -> bool:
824        """Returns True if the constraint is always false.
825
826        Usually, it means that it was created by model.add(False)
827        """
828        return self.lower_bound > self.upper_bound
829
830    def __str__(self):
831        return self.name
832
833    def __repr__(self):
834        return (
835            f"EnforcedLinearConstraint({self.name}, lb={self.lower_bound},"
836            f" ub={self.upper_bound},"
837            f" var_indices={self.helper.enforced_constraint_var_indices(self.index)},"
838            f" coefficients={self.helper.enforced_constraint_coefficients(self.index)},"
839            f" indicator_variable={self.indicator_variable}"
840            f" indicator_value={self.indicator_value})"
841        )
842
843    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
844        """Sets the coefficient of the variable in the constraint."""
845        if self.is_always_false():
846            raise ValueError(
847                f"Constraint {self.index} is always false and cannot be modified"
848            )
849        self.__helper.set_enforced_constraint_coefficient(
850            self.__index, var.index, coeff
851        )
852
853    def add_term(self, var: Variable, coeff: NumberT) -> None:
854        """Adds var * coeff to the constraint."""
855        if self.is_always_false():
856            raise ValueError(
857                f"Constraint {self.index} is always false and cannot be modified"
858            )
859        self.__helper.safe_add_term_to_enforced_constraint(
860            self.__index, var.index, coeff
861        )
862
863    def clear_terms(self) -> None:
864        """Clear all terms of the constraint."""
865        self.__helper.clear_enforced_constraint_terms(self.__index)

Stores an enforced linear equation, also name indicator constraint.

Example:

x = model.new_num_var(0, 10, 'x') y = model.new_num_var(0, 10, 'y') z = model.new_bool_var('z')

enforced_linear_constraint = model.add_enforced(x + 2 * y == 5, z, False)

EnforcedLinearConstraint( helper: ortools.linear_solver.python.model_builder_helper.ModelBuilderHelper, index: Union[int, numbers.Integral, numpy.integer, NoneType] = None)
750    def __init__(
751        self, helper: mbh.ModelBuilderHelper, index: Optional[IntegerT] = None
752    ):
753        if index is None:
754            self.__index = helper.add_enforced_linear_constraint()
755        else:
756            if not helper.is_enforced_linear_constraint(index):
757                raise ValueError(
758                    f"the given index {index} does not refer to an enforced linear"
759                    " constraint"
760                )
761
762            self.__index = index
763        self.__helper: mbh.ModelBuilderHelper = helper
index: Union[int, numbers.Integral, numpy.integer]
765    @property
766    def index(self) -> IntegerT:
767        """Returns the index of the constraint in the helper."""
768        return self.__index

Returns the index of the constraint in the helper.

770    @property
771    def helper(self) -> mbh.ModelBuilderHelper:
772        """Returns the ModelBuilderHelper instance."""
773        return self.__helper

Returns the ModelBuilderHelper instance.

lower_bound: numpy.float64
775    @property
776    def lower_bound(self) -> np.double:
777        return self.__helper.enforced_constraint_lower_bound(self.__index)
upper_bound: numpy.float64
783    @property
784    def upper_bound(self) -> np.double:
785        return self.__helper.enforced_constraint_upper_bound(self.__index)
indicator_variable: Variable
791    @property
792    def indicator_variable(self) -> "Variable":
793        enforcement_var_index = (
794            self.__helper.enforced_constraint_indicator_variable_index(self.__index)
795        )
796        return Variable(self.__helper, enforcement_var_index, None, None, None)
indicator_value: bool
804    @property
805    def indicator_value(self) -> bool:
806        return self.__helper.enforced_constraint_indicator_value(self.__index)
name: str
812    @property
813    def name(self) -> str:
814        constraint_name = self.__helper.enforced_constraint_name(self.__index)
815        if constraint_name:
816            return constraint_name
817        return f"enforced_linear_constraint#{self.__index}"
def is_always_false(self) -> bool:
823    def is_always_false(self) -> bool:
824        """Returns True if the constraint is always false.
825
826        Usually, it means that it was created by model.add(False)
827        """
828        return self.lower_bound > self.upper_bound

Returns True if the constraint is always false.

Usually, it means that it was created by model.add(False)

def set_coefficient( self, var: Variable, coeff: Union[int, float, numbers.Real, numpy.number]) -> None:
843    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
844        """Sets the coefficient of the variable in the constraint."""
845        if self.is_always_false():
846            raise ValueError(
847                f"Constraint {self.index} is always false and cannot be modified"
848            )
849        self.__helper.set_enforced_constraint_coefficient(
850            self.__index, var.index, coeff
851        )

Sets the coefficient of the variable in the constraint.

def add_term( self, var: Variable, coeff: Union[int, float, numbers.Real, numpy.number]) -> None:
853    def add_term(self, var: Variable, coeff: NumberT) -> None:
854        """Adds var * coeff to the constraint."""
855        if self.is_always_false():
856            raise ValueError(
857                f"Constraint {self.index} is always false and cannot be modified"
858            )
859        self.__helper.safe_add_term_to_enforced_constraint(
860            self.__index, var.index, coeff
861        )

Adds var * coeff to the constraint.

def clear_terms(self) -> None:
863    def clear_terms(self) -> None:
864        """Clear all terms of the constraint."""
865        self.__helper.clear_enforced_constraint_terms(self.__index)

Clear all terms of the constraint.

class Model:
 868class Model:
 869    """Methods for building a linear model.
 870
 871    Methods beginning with:
 872
 873    * ```new_``` create integer, boolean, or interval variables.
 874    * ```add_``` create new constraints and add them to the model.
 875    """
 876
 877    def __init__(self):
 878        self.__helper: mbh.ModelBuilderHelper = mbh.ModelBuilderHelper()
 879
 880    def clone(self) -> "Model":
 881        """Returns a clone of the current model."""
 882        clone = Model()
 883        clone.helper.overwrite_model(self.helper)
 884        return clone
 885
 886    @typing.overload
 887    def _get_linear_constraints(self, constraints: Optional[pd.Index]) -> pd.Index:
 888        ...
 889
 890    @typing.overload
 891    def _get_linear_constraints(self, constraints: pd.Series) -> pd.Series:
 892        ...
 893
 894    def _get_linear_constraints(
 895        self, constraints: Optional[_IndexOrSeries] = None
 896    ) -> _IndexOrSeries:
 897        if constraints is None:
 898            return self.get_linear_constraints()
 899        return constraints
 900
 901    @typing.overload
 902    def _get_variables(self, variables: Optional[pd.Index]) -> pd.Index:
 903        ...
 904
 905    @typing.overload
 906    def _get_variables(self, variables: pd.Series) -> pd.Series:
 907        ...
 908
 909    def _get_variables(
 910        self, variables: Optional[_IndexOrSeries] = None
 911    ) -> _IndexOrSeries:
 912        if variables is None:
 913            return self.get_variables()
 914        return variables
 915
 916    def get_linear_constraints(self) -> pd.Index:
 917        """Gets all linear constraints in the model."""
 918        return pd.Index(
 919            [self.linear_constraint_from_index(i) for i in range(self.num_constraints)],
 920            name="linear_constraint",
 921        )
 922
 923    def get_linear_constraint_expressions(
 924        self, constraints: Optional[_IndexOrSeries] = None
 925    ) -> pd.Series:
 926        """Gets the expressions of all linear constraints in the set.
 927
 928        If `constraints` is a `pd.Index`, then the output will be indexed by the
 929        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 930        dimensions, then the output will be indexed by the same underlying
 931        dimensions.
 932
 933        Args:
 934          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 935            constraints from which to get the expressions. If unspecified, all
 936            linear constraints will be in scope.
 937
 938        Returns:
 939          pd.Series: The expressions of all linear constraints in the set.
 940        """
 941        return _attribute_series(
 942            # pylint: disable=g-long-lambda
 943            func=lambda c: _as_flat_linear_expression(
 944                # pylint: disable=g-complex-comprehension
 945                sum(
 946                    coeff * Variable(self.__helper, var_id, None, None, None)
 947                    for var_id, coeff in zip(
 948                        c.helper.constraint_var_indices(c.index),
 949                        c.helper.constraint_coefficients(c.index),
 950                    )
 951                )
 952            ),
 953            values=self._get_linear_constraints(constraints),
 954        )
 955
 956    def get_linear_constraint_lower_bounds(
 957        self, constraints: Optional[_IndexOrSeries] = None
 958    ) -> pd.Series:
 959        """Gets the lower bounds of all linear constraints in the set.
 960
 961        If `constraints` is a `pd.Index`, then the output will be indexed by the
 962        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 963        dimensions, then the output will be indexed by the same underlying
 964        dimensions.
 965
 966        Args:
 967          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 968            constraints from which to get the lower bounds. If unspecified, all
 969            linear constraints will be in scope.
 970
 971        Returns:
 972          pd.Series: The lower bounds of all linear constraints in the set.
 973        """
 974        return _attribute_series(
 975            func=lambda c: c.lower_bound,  # pylint: disable=protected-access
 976            values=self._get_linear_constraints(constraints),
 977        )
 978
 979    def get_linear_constraint_upper_bounds(
 980        self, constraints: Optional[_IndexOrSeries] = None
 981    ) -> pd.Series:
 982        """Gets the upper bounds of all linear constraints in the set.
 983
 984        If `constraints` is a `pd.Index`, then the output will be indexed by the
 985        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 986        dimensions, then the output will be indexed by the same underlying
 987        dimensions.
 988
 989        Args:
 990          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 991            constraints. If unspecified, all linear constraints will be in scope.
 992
 993        Returns:
 994          pd.Series: The upper bounds of all linear constraints in the set.
 995        """
 996        return _attribute_series(
 997            func=lambda c: c.upper_bound,  # pylint: disable=protected-access
 998            values=self._get_linear_constraints(constraints),
 999        )
1000
1001    def get_variables(self) -> pd.Index:
1002        """Gets all variables in the model."""
1003        return pd.Index(
1004            [self.var_from_index(i) for i in range(self.num_variables)],
1005            name="variable",
1006        )
1007
1008    def get_variable_lower_bounds(
1009        self, variables: Optional[_IndexOrSeries] = None
1010    ) -> pd.Series:
1011        """Gets the lower bounds of all variables in the set.
1012
1013        If `variables` is a `pd.Index`, then the output will be indexed by the
1014        variables. If `variables` is a `pd.Series` indexed by the underlying
1015        dimensions, then the output will be indexed by the same underlying
1016        dimensions.
1017
1018        Args:
1019          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
1020            from which to get the lower bounds. If unspecified, all variables will
1021            be in scope.
1022
1023        Returns:
1024          pd.Series: The lower bounds of all variables in the set.
1025        """
1026        return _attribute_series(
1027            func=lambda v: v.lower_bound,  # pylint: disable=protected-access
1028            values=self._get_variables(variables),
1029        )
1030
1031    def get_variable_upper_bounds(
1032        self, variables: Optional[_IndexOrSeries] = None
1033    ) -> pd.Series:
1034        """Gets the upper bounds of all variables in the set.
1035
1036        Args:
1037          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
1038            from which to get the upper bounds. If unspecified, all variables will
1039            be in scope.
1040
1041        Returns:
1042          pd.Series: The upper bounds of all variables in the set.
1043        """
1044        return _attribute_series(
1045            func=lambda v: v.upper_bound,  # pylint: disable=protected-access
1046            values=self._get_variables(variables),
1047        )
1048
1049    # Integer variable.
1050
1051    def new_var(
1052        self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str]
1053    ) -> Variable:
1054        """Create an integer variable with domain [lb, ub].
1055
1056        Args:
1057          lb: Lower bound of the variable.
1058          ub: Upper bound of the variable.
1059          is_integer: Indicates if the variable must take integral values.
1060          name: The name of the variable.
1061
1062        Returns:
1063          a variable whose domain is [lb, ub].
1064        """
1065
1066        return Variable(self.__helper, lb, ub, is_integer, name)
1067
1068    def new_int_var(
1069        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
1070    ) -> Variable:
1071        """Create an integer variable with domain [lb, ub].
1072
1073        Args:
1074          lb: Lower bound of the variable.
1075          ub: Upper bound of the variable.
1076          name: The name of the variable.
1077
1078        Returns:
1079          a variable whose domain is [lb, ub].
1080        """
1081
1082        return self.new_var(lb, ub, True, name)
1083
1084    def new_num_var(
1085        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
1086    ) -> Variable:
1087        """Create an integer variable with domain [lb, ub].
1088
1089        Args:
1090          lb: Lower bound of the variable.
1091          ub: Upper bound of the variable.
1092          name: The name of the variable.
1093
1094        Returns:
1095          a variable whose domain is [lb, ub].
1096        """
1097
1098        return self.new_var(lb, ub, False, name)
1099
1100    def new_bool_var(self, name: Optional[str] = None) -> Variable:
1101        """Creates a 0-1 variable with the given name."""
1102        return self.new_var(
1103            0, 1, True, name
1104        )  # pytype: disable=wrong-arg-types  # numpy-scalars
1105
1106    def new_constant(self, value: NumberT) -> Variable:
1107        """Declares a constant variable."""
1108        return self.new_var(value, value, False, None)
1109
1110    def new_var_series(
1111        self,
1112        name: str,
1113        index: pd.Index,
1114        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1115        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1116        is_integral: Union[bool, pd.Series] = False,
1117    ) -> pd.Series:
1118        """Creates a series of (scalar-valued) variables with the given name.
1119
1120        Args:
1121          name (str): Required. The name of the variable set.
1122          index (pd.Index): Required. The index to use for the variable set.
1123          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1124            variables in the set. If a `pd.Series` is passed in, it will be based on
1125            the corresponding values of the pd.Series. Defaults to -inf.
1126          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1127            variables in the set. If a `pd.Series` is passed in, it will be based on
1128            the corresponding values of the pd.Series. Defaults to +inf.
1129          is_integral (bool, pd.Series): Optional. Indicates if the variable can
1130            only take integer values. If a `pd.Series` is passed in, it will be
1131            based on the corresponding values of the pd.Series. Defaults to False.
1132
1133        Returns:
1134          pd.Series: The variable set indexed by its corresponding dimensions.
1135
1136        Raises:
1137          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1138          ValueError: if the `name` is not a valid identifier or already exists.
1139          ValueError: if the `lowerbound` is greater than the `upperbound`.
1140          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1141          does not match the input index.
1142        """
1143        if not isinstance(index, pd.Index):
1144            raise TypeError("Non-index object is used as index")
1145        if not name.isidentifier():
1146            raise ValueError("name={} is not a valid identifier".format(name))
1147        if (
1148            mbn.is_a_number(lower_bounds)
1149            and mbn.is_a_number(upper_bounds)
1150            and lower_bounds > upper_bounds
1151        ):
1152            raise ValueError(
1153                "lower_bound={} is greater than upper_bound={} for variable set={}".format(
1154                    lower_bounds, upper_bounds, name
1155                )
1156            )
1157        if (
1158            isinstance(is_integral, bool)
1159            and is_integral
1160            and mbn.is_a_number(lower_bounds)
1161            and mbn.is_a_number(upper_bounds)
1162            and math.isfinite(lower_bounds)
1163            and math.isfinite(upper_bounds)
1164            and math.ceil(lower_bounds) > math.floor(upper_bounds)
1165        ):
1166            raise ValueError(
1167                "ceil(lower_bound={})={}".format(lower_bounds, math.ceil(lower_bounds))
1168                + " is greater than floor("
1169                + "upper_bound={})={}".format(upper_bounds, math.floor(upper_bounds))
1170                + " for variable set={}".format(name)
1171            )
1172        lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index)
1173        upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index)
1174        is_integrals = _convert_to_series_and_validate_index(is_integral, index)
1175        return pd.Series(
1176            index=index,
1177            data=[
1178                # pylint: disable=g-complex-comprehension
1179                Variable(
1180                    helper=self.__helper,
1181                    name=f"{name}[{i}]",
1182                    lb=lower_bounds[i],
1183                    ub=upper_bounds[i],
1184                    is_integral=is_integrals[i],
1185                )
1186                for i in index
1187            ],
1188        )
1189
1190    def new_num_var_series(
1191        self,
1192        name: str,
1193        index: pd.Index,
1194        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1195        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1196    ) -> pd.Series:
1197        """Creates a series of continuous variables with the given name.
1198
1199        Args:
1200          name (str): Required. The name of the variable set.
1201          index (pd.Index): Required. The index to use for the variable set.
1202          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1203            variables in the set. If a `pd.Series` is passed in, it will be based on
1204            the corresponding values of the pd.Series. Defaults to -inf.
1205          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1206            variables in the set. If a `pd.Series` is passed in, it will be based on
1207            the corresponding values of the pd.Series. Defaults to +inf.
1208
1209        Returns:
1210          pd.Series: The variable set indexed by its corresponding 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 `lowerbound` is greater than the `upperbound`.
1216          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1217          does not match the input index.
1218        """
1219        return self.new_var_series(name, index, lower_bounds, upper_bounds, False)
1220
1221    def new_int_var_series(
1222        self,
1223        name: str,
1224        index: pd.Index,
1225        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1226        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1227    ) -> pd.Series:
1228        """Creates a series of integer variables with the given name.
1229
1230        Args:
1231          name (str): Required. The name of the variable set.
1232          index (pd.Index): Required. The index to use for the variable set.
1233          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1234            variables in the set. If a `pd.Series` is passed in, it will be based on
1235            the corresponding values of the pd.Series. Defaults to -inf.
1236          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1237            variables in the set. If a `pd.Series` is passed in, it will be based on
1238            the corresponding values of the pd.Series. Defaults to +inf.
1239
1240        Returns:
1241          pd.Series: The variable set indexed by its corresponding dimensions.
1242
1243        Raises:
1244          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1245          ValueError: if the `name` is not a valid identifier or already exists.
1246          ValueError: if the `lowerbound` is greater than the `upperbound`.
1247          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1248          does not match the input index.
1249        """
1250        return self.new_var_series(name, index, lower_bounds, upper_bounds, True)
1251
1252    def new_bool_var_series(
1253        self,
1254        name: str,
1255        index: pd.Index,
1256    ) -> pd.Series:
1257        """Creates a series of Boolean variables with the given name.
1258
1259        Args:
1260          name (str): Required. The name of the variable set.
1261          index (pd.Index): Required. The index to use for the variable set.
1262
1263        Returns:
1264          pd.Series: The variable set indexed by its corresponding dimensions.
1265
1266        Raises:
1267          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1268          ValueError: if the `name` is not a valid identifier or already exists.
1269          ValueError: if the `lowerbound` is greater than the `upperbound`.
1270          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1271          does not match the input index.
1272        """
1273        return self.new_var_series(name, index, 0, 1, True)
1274
1275    def var_from_index(self, index: IntegerT) -> Variable:
1276        """Rebuilds a variable object from the model and its index."""
1277        return Variable(self.__helper, index, None, None, None)
1278
1279    # Linear constraints.
1280
1281    def add_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
1282        self,
1283        linear_expr: LinearExprT,
1284        lb: NumberT = -math.inf,
1285        ub: NumberT = math.inf,
1286        name: Optional[str] = None,
1287    ) -> LinearConstraint:
1288        """Adds the constraint: `lb <= linear_expr <= ub` with the given name."""
1289        ct = LinearConstraint(self.__helper)
1290        if name:
1291            self.__helper.set_constraint_name(ct.index, name)
1292        if mbn.is_a_number(linear_expr):
1293            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
1294            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
1295        elif isinstance(linear_expr, Variable):
1296            self.__helper.set_constraint_lower_bound(ct.index, lb)
1297            self.__helper.set_constraint_upper_bound(ct.index, ub)
1298            self.__helper.add_term_to_constraint(ct.index, linear_expr.index, 1.0)
1299        elif isinstance(linear_expr, LinearExpr):
1300            flat_expr = _as_flat_linear_expression(linear_expr)
1301            # pylint: disable=protected-access
1302            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr._offset)
1303            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr._offset)
1304            self.__helper.add_terms_to_constraint(
1305                ct.index, flat_expr._variable_indices, flat_expr._coefficients
1306            )
1307        else:
1308            raise TypeError(
1309                f"Not supported: Model.add_linear_constraint({linear_expr})"
1310                f" with type {type(linear_expr)}"
1311            )
1312        return ct
1313
1314    def add(
1315        self, ct: Union[ConstraintT, pd.Series], name: Optional[str] = None
1316    ) -> Union[LinearConstraint, pd.Series]:
1317        """Adds a `BoundedLinearExpression` to the model.
1318
1319        Args:
1320          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
1321          name: An optional name.
1322
1323        Returns:
1324          An instance of the `Constraint` class.
1325
1326        Note that a special treatment is done when the argument does not contain any
1327        variable, and thus evaluates to True or False.
1328
1329        model.add(True) will create a constraint 0 <= empty sum <= 0
1330
1331        model.add(False) will create a constraint inf <= empty sum <= -inf
1332
1333        you can check the if a constraint is always false (lb=inf, ub=-inf) by
1334        calling LinearConstraint.is_always_false()
1335        """
1336        if isinstance(ct, _BoundedLinearExpr):
1337            return ct._add_linear_constraint(self.__helper, name)
1338        elif isinstance(ct, bool):
1339            return _add_linear_constraint_to_helper(ct, self.__helper, name)
1340        elif isinstance(ct, pd.Series):
1341            return pd.Series(
1342                index=ct.index,
1343                data=[
1344                    _add_linear_constraint_to_helper(
1345                        expr, self.__helper, f"{name}[{i}]"
1346                    )
1347                    for (i, expr) in zip(ct.index, ct)
1348                ],
1349            )
1350        else:
1351            raise TypeError("Not supported: Model.add(" + str(ct) + ")")
1352
1353    def linear_constraint_from_index(self, index: IntegerT) -> LinearConstraint:
1354        """Rebuilds a linear constraint object from the model and its index."""
1355        return LinearConstraint(self.__helper, index)
1356
1357    # EnforcedLinear constraints.
1358
1359    def add_enforced_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
1360        self,
1361        linear_expr: LinearExprT,
1362        ivar: "Variable",
1363        ivalue: bool,
1364        lb: NumberT = -math.inf,
1365        ub: NumberT = math.inf,
1366        name: Optional[str] = None,
1367    ) -> EnforcedLinearConstraint:
1368        """Adds the constraint: `ivar == ivalue => lb <= linear_expr <= ub` with the given name."""
1369        ct = EnforcedLinearConstraint(self.__helper)
1370        ct.indicator_variable = ivar
1371        ct.indicator_value = ivalue
1372        if name:
1373            self.__helper.set_constraint_name(ct.index, name)
1374        if mbn.is_a_number(linear_expr):
1375            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
1376            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
1377        elif isinstance(linear_expr, Variable):
1378            self.__helper.set_constraint_lower_bound(ct.index, lb)
1379            self.__helper.set_constraint_upper_bound(ct.index, ub)
1380            self.__helper.add_term_to_constraint(ct.index, linear_expr.index, 1.0)
1381        elif isinstance(linear_expr, LinearExpr):
1382            flat_expr = _as_flat_linear_expression(linear_expr)
1383            # pylint: disable=protected-access
1384            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr._offset)
1385            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr._offset)
1386            self.__helper.add_terms_to_constraint(
1387                ct.index, flat_expr._variable_indices, flat_expr._coefficients
1388            )
1389        else:
1390            raise TypeError(
1391                "Not supported:"
1392                f" Model.add_enforced_linear_constraint({linear_expr}) with"
1393                f" type {type(linear_expr)}"
1394            )
1395        return ct
1396
1397    def add_enforced(
1398        self,
1399        ct: Union[ConstraintT, pd.Series],
1400        var: Union[Variable, pd.Series],
1401        value: Union[bool, pd.Series],
1402        name: Optional[str] = None,
1403    ) -> Union[EnforcedLinearConstraint, pd.Series]:
1404        """Adds a `ivar == ivalue => BoundedLinearExpression` to the model.
1405
1406        Args:
1407          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
1408          var: The indicator variable
1409          value: the indicator value
1410          name: An optional name.
1411
1412        Returns:
1413          An instance of the `Constraint` class.
1414
1415        Note that a special treatment is done when the argument does not contain any
1416        variable, and thus evaluates to True or False.
1417
1418        model.add_enforced(True, ivar, ivalue) will create a constraint 0 <= empty
1419        sum <= 0
1420
1421        model.add_enforced(False, var, value) will create a constraint inf <=
1422        empty sum <= -inf
1423
1424        you can check the if a constraint is always false (lb=inf, ub=-inf) by
1425        calling EnforcedLinearConstraint.is_always_false()
1426        """
1427        if isinstance(ct, _BoundedLinearExpr):
1428            return ct._add_enforced_linear_constraint(self.__helper, var, value, name)
1429        elif (
1430            isinstance(ct, bool)
1431            and isinstance(var, Variable)
1432            and isinstance(value, bool)
1433        ):
1434            return _add_enforced_linear_constraint_to_helper(
1435                ct, self.__helper, var, value, name
1436            )
1437        elif isinstance(ct, pd.Series):
1438            ivar_series = _convert_to_var_series_and_validate_index(var, ct.index)
1439            ivalue_series = _convert_to_series_and_validate_index(value, ct.index)
1440            return pd.Series(
1441                index=ct.index,
1442                data=[
1443                    _add_enforced_linear_constraint_to_helper(
1444                        expr,
1445                        self.__helper,
1446                        ivar_series[i],
1447                        ivalue_series[i],
1448                        f"{name}[{i}]",
1449                    )
1450                    for (i, expr) in zip(ct.index, ct)
1451                ],
1452            )
1453        else:
1454            raise TypeError("Not supported: Model.add_enforced(" + str(ct) + ")")
1455
1456    def enforced_linear_constraint_from_index(
1457        self, index: IntegerT
1458    ) -> EnforcedLinearConstraint:
1459        """Rebuilds an enforced linear constraint object from the model and its index."""
1460        return EnforcedLinearConstraint(self.__helper, index)
1461
1462    # Objective.
1463    def minimize(self, linear_expr: LinearExprT) -> None:
1464        """Minimizes the given objective."""
1465        self.__optimize(linear_expr, False)
1466
1467    def maximize(self, linear_expr: LinearExprT) -> None:
1468        """Maximizes the given objective."""
1469        self.__optimize(linear_expr, True)
1470
1471    def __optimize(self, linear_expr: LinearExprT, maximize: bool) -> None:
1472        """Defines the objective."""
1473        self.helper.clear_objective()
1474        self.__helper.set_maximize(maximize)
1475        if mbn.is_a_number(linear_expr):
1476            self.helper.set_objective_offset(linear_expr)
1477        elif isinstance(linear_expr, Variable):
1478            self.helper.set_var_objective_coefficient(linear_expr.index, 1.0)
1479        elif isinstance(linear_expr, LinearExpr):
1480            flat_expr = _as_flat_linear_expression(linear_expr)
1481            # pylint: disable=protected-access
1482            self.helper.set_objective_offset(flat_expr._offset)
1483            self.helper.set_objective_coefficients(
1484                flat_expr._variable_indices, flat_expr._coefficients
1485            )
1486        else:
1487            raise TypeError(f"Not supported: Model.minimize/maximize({linear_expr})")
1488
1489    @property
1490    def objective_offset(self) -> np.double:
1491        """Returns the fixed offset of the objective."""
1492        return self.__helper.objective_offset()
1493
1494    @objective_offset.setter
1495    def objective_offset(self, value: NumberT) -> None:
1496        self.__helper.set_objective_offset(value)
1497
1498    def objective_expression(self) -> "_LinearExpression":
1499        """Returns the expression to optimize."""
1500        return _as_flat_linear_expression(
1501            sum(
1502                variable * self.__helper.var_objective_coefficient(variable.index)
1503                for variable in self.get_variables()
1504                if self.__helper.var_objective_coefficient(variable.index) != 0.0
1505            )
1506            + self.__helper.objective_offset()
1507        )
1508
1509    # Hints.
1510    def clear_hints(self):
1511        """Clears all solution hints."""
1512        self.__helper.clear_hints()
1513
1514    def add_hint(self, var: Variable, value: NumberT) -> None:
1515        """Adds var == value as a hint to the model.
1516
1517        Args:
1518          var: The variable of the hint
1519          value: The value of the hint
1520
1521        Note that variables must not appear more than once in the list of hints.
1522        """
1523        self.__helper.add_hint(var.index, value)
1524
1525    # Input/Output
1526    def export_to_lp_string(self, obfuscate: bool = False) -> str:
1527        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1528        options.obfuscate = obfuscate
1529        return self.__helper.export_to_lp_string(options)
1530
1531    def export_to_mps_string(self, obfuscate: bool = False) -> str:
1532        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1533        options.obfuscate = obfuscate
1534        return self.__helper.export_to_mps_string(options)
1535
1536    def export_to_proto(self) -> linear_solver_pb2.MPModelProto:
1537        """Exports the optimization model to a ProtoBuf format."""
1538        return mbh.to_mpmodel_proto(self.__helper)
1539
1540    def import_from_mps_string(self, mps_string: str) -> bool:
1541        """Reads a model from a MPS string."""
1542        return self.__helper.import_from_mps_string(mps_string)
1543
1544    def import_from_mps_file(self, mps_file: str) -> bool:
1545        """Reads a model from a .mps file."""
1546        return self.__helper.import_from_mps_file(mps_file)
1547
1548    def import_from_lp_string(self, lp_string: str) -> bool:
1549        """Reads a model from a LP string."""
1550        return self.__helper.import_from_lp_string(lp_string)
1551
1552    def import_from_lp_file(self, lp_file: str) -> bool:
1553        """Reads a model from a .lp file."""
1554        return self.__helper.import_from_lp_file(lp_file)
1555
1556    def import_from_proto_file(self, proto_file: str) -> bool:
1557        """Reads a model from a proto file."""
1558        return self.__helper.read_model_from_proto_file(proto_file)
1559
1560    def export_to_proto_file(self, proto_file: str) -> bool:
1561        """Writes a model to a proto file."""
1562        return self.__helper.write_model_to_proto_file(proto_file)
1563
1564    # Model getters and Setters
1565
1566    @property
1567    def num_variables(self) -> int:
1568        """Returns the number of variables in the model."""
1569        return self.__helper.num_variables()
1570
1571    @property
1572    def num_constraints(self) -> int:
1573        """The number of constraints in the model."""
1574        return self.__helper.num_constraints()
1575
1576    @property
1577    def name(self) -> str:
1578        """The name of the model."""
1579        return self.__helper.name()
1580
1581    @name.setter
1582    def name(self, name: str):
1583        self.__helper.set_name(name)
1584
1585    @property
1586    def helper(self) -> mbh.ModelBuilderHelper:
1587        """Returns the model builder helper."""
1588        return self.__helper

Methods for building a linear model.

Methods beginning with:

  • new_ create integer, boolean, or interval variables.
  • add_ create new constraints and add them to the model.
def clone(self) -> Model:
880    def clone(self) -> "Model":
881        """Returns a clone of the current model."""
882        clone = Model()
883        clone.helper.overwrite_model(self.helper)
884        return clone

Returns a clone of the current model.

def get_linear_constraints(self) -> pandas.core.indexes.base.Index:
916    def get_linear_constraints(self) -> pd.Index:
917        """Gets all linear constraints in the model."""
918        return pd.Index(
919            [self.linear_constraint_from_index(i) for i in range(self.num_constraints)],
920            name="linear_constraint",
921        )

Gets all linear constraints in the model.

def get_linear_constraint_expressions( self, constraints: Union[pandas.core.indexes.base.Index, pandas.core.series.Series, NoneType] = None) -> pandas.core.series.Series:
923    def get_linear_constraint_expressions(
924        self, constraints: Optional[_IndexOrSeries] = None
925    ) -> pd.Series:
926        """Gets the expressions of all linear constraints in the set.
927
928        If `constraints` is a `pd.Index`, then the output will be indexed by the
929        constraints. If `constraints` is a `pd.Series` indexed by the underlying
930        dimensions, then the output will be indexed by the same underlying
931        dimensions.
932
933        Args:
934          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
935            constraints from which to get the expressions. If unspecified, all
936            linear constraints will be in scope.
937
938        Returns:
939          pd.Series: The expressions of all linear constraints in the set.
940        """
941        return _attribute_series(
942            # pylint: disable=g-long-lambda
943            func=lambda c: _as_flat_linear_expression(
944                # pylint: disable=g-complex-comprehension
945                sum(
946                    coeff * Variable(self.__helper, var_id, None, None, None)
947                    for var_id, coeff in zip(
948                        c.helper.constraint_var_indices(c.index),
949                        c.helper.constraint_coefficients(c.index),
950                    )
951                )
952            ),
953            values=self._get_linear_constraints(constraints),
954        )

Gets the expressions of all linear constraints in the set.

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

Arguments:
  • constraints (Union[pd.Index, pd.Series]): Optional. The set of linear constraints from which to get the expressions. If unspecified, all linear constraints will be in scope.
Returns:

pd.Series: The expressions of all linear constraints in the set.

def get_linear_constraint_lower_bounds( self, constraints: Union[pandas.core.indexes.base.Index, pandas.core.series.Series, NoneType] = None) -> pandas.core.series.Series:
956    def get_linear_constraint_lower_bounds(
957        self, constraints: Optional[_IndexOrSeries] = None
958    ) -> pd.Series:
959        """Gets the lower bounds of all linear constraints in the set.
960
961        If `constraints` is a `pd.Index`, then the output will be indexed by the
962        constraints. If `constraints` is a `pd.Series` indexed by the underlying
963        dimensions, then the output will be indexed by the same underlying
964        dimensions.
965
966        Args:
967          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
968            constraints from which to get the lower bounds. If unspecified, all
969            linear constraints will be in scope.
970
971        Returns:
972          pd.Series: The lower bounds of all linear constraints in the set.
973        """
974        return _attribute_series(
975            func=lambda c: c.lower_bound,  # pylint: disable=protected-access
976            values=self._get_linear_constraints(constraints),
977        )

Gets the lower bounds of all linear constraints in the set.

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

Arguments:
  • constraints (Union[pd.Index, pd.Series]): Optional. The set of linear constraints from which to get the lower bounds. If unspecified, all linear constraints will be in scope.
Returns:

pd.Series: The lower bounds of all linear constraints in the set.

def get_linear_constraint_upper_bounds( self, constraints: Union[pandas.core.indexes.base.Index, pandas.core.series.Series, NoneType] = None) -> pandas.core.series.Series:
979    def get_linear_constraint_upper_bounds(
980        self, constraints: Optional[_IndexOrSeries] = None
981    ) -> pd.Series:
982        """Gets the upper bounds of all linear constraints in the set.
983
984        If `constraints` is a `pd.Index`, then the output will be indexed by the
985        constraints. If `constraints` is a `pd.Series` indexed by the underlying
986        dimensions, then the output will be indexed by the same underlying
987        dimensions.
988
989        Args:
990          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
991            constraints. If unspecified, all linear constraints will be in scope.
992
993        Returns:
994          pd.Series: The upper bounds of all linear constraints in the set.
995        """
996        return _attribute_series(
997            func=lambda c: c.upper_bound,  # pylint: disable=protected-access
998            values=self._get_linear_constraints(constraints),
999        )

Gets the upper bounds of all linear constraints in the set.

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

Arguments:
  • constraints (Union[pd.Index, pd.Series]): Optional. The set of linear constraints. If unspecified, all linear constraints will be in scope.
Returns:

pd.Series: The upper bounds of all linear constraints in the set.

def get_variables(self) -> pandas.core.indexes.base.Index:
1001    def get_variables(self) -> pd.Index:
1002        """Gets all variables in the model."""
1003        return pd.Index(
1004            [self.var_from_index(i) for i in range(self.num_variables)],
1005            name="variable",
1006        )

Gets all variables in the model.

def get_variable_lower_bounds( self, variables: Union[pandas.core.indexes.base.Index, pandas.core.series.Series, NoneType] = None) -> pandas.core.series.Series:
1008    def get_variable_lower_bounds(
1009        self, variables: Optional[_IndexOrSeries] = None
1010    ) -> pd.Series:
1011        """Gets the lower bounds of all variables in the set.
1012
1013        If `variables` is a `pd.Index`, then the output will be indexed by the
1014        variables. If `variables` is a `pd.Series` indexed by the underlying
1015        dimensions, then the output will be indexed by the same underlying
1016        dimensions.
1017
1018        Args:
1019          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
1020            from which to get the lower bounds. If unspecified, all variables will
1021            be in scope.
1022
1023        Returns:
1024          pd.Series: The lower bounds of all variables in the set.
1025        """
1026        return _attribute_series(
1027            func=lambda v: v.lower_bound,  # pylint: disable=protected-access
1028            values=self._get_variables(variables),
1029        )

Gets the lower bounds of all variables in the set.

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]): Optional. The set of variables from which to get the lower bounds. If unspecified, all variables will be in scope.
Returns:

pd.Series: The lower bounds of all variables in the set.

def get_variable_upper_bounds( self, variables: Union[pandas.core.indexes.base.Index, pandas.core.series.Series, NoneType] = None) -> pandas.core.series.Series:
1031    def get_variable_upper_bounds(
1032        self, variables: Optional[_IndexOrSeries] = None
1033    ) -> pd.Series:
1034        """Gets the upper bounds of all variables in the set.
1035
1036        Args:
1037          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
1038            from which to get the upper bounds. If unspecified, all variables will
1039            be in scope.
1040
1041        Returns:
1042          pd.Series: The upper bounds of all variables in the set.
1043        """
1044        return _attribute_series(
1045            func=lambda v: v.upper_bound,  # pylint: disable=protected-access
1046            values=self._get_variables(variables),
1047        )

Gets the upper bounds of all variables in the set.

Arguments:
  • variables (Union[pd.Index, pd.Series]): Optional. The set of variables from which to get the upper bounds. If unspecified, all variables will be in scope.
Returns:

pd.Series: The upper bounds of all variables in the set.

def new_var( self, lb: Union[int, float, numbers.Real, numpy.number], ub: Union[int, float, numbers.Real, numpy.number], is_integer: bool, name: Optional[str]) -> Variable:
1051    def new_var(
1052        self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str]
1053    ) -> Variable:
1054        """Create an integer variable with domain [lb, ub].
1055
1056        Args:
1057          lb: Lower bound of the variable.
1058          ub: Upper bound of the variable.
1059          is_integer: Indicates if the variable must take integral values.
1060          name: The name of the variable.
1061
1062        Returns:
1063          a variable whose domain is [lb, ub].
1064        """
1065
1066        return Variable(self.__helper, lb, ub, is_integer, name)

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

Arguments:
  • lb: Lower bound of the variable.
  • ub: Upper bound of the variable.
  • is_integer: Indicates if the variable must take integral values.
  • name: The name of the variable.
Returns:

a variable whose domain is [lb, ub].

def new_int_var( self, lb: Union[int, float, numbers.Real, numpy.number], ub: Union[int, float, numbers.Real, numpy.number], name: Optional[str] = None) -> Variable:
1068    def new_int_var(
1069        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
1070    ) -> Variable:
1071        """Create an integer variable with domain [lb, ub].
1072
1073        Args:
1074          lb: Lower bound of the variable.
1075          ub: Upper bound of the variable.
1076          name: The name of the variable.
1077
1078        Returns:
1079          a variable whose domain is [lb, ub].
1080        """
1081
1082        return self.new_var(lb, ub, True, name)

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

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

a variable whose domain is [lb, ub].

def new_num_var( self, lb: Union[int, float, numbers.Real, numpy.number], ub: Union[int, float, numbers.Real, numpy.number], name: Optional[str] = None) -> Variable:
1084    def new_num_var(
1085        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
1086    ) -> Variable:
1087        """Create an integer variable with domain [lb, ub].
1088
1089        Args:
1090          lb: Lower bound of the variable.
1091          ub: Upper bound of the variable.
1092          name: The name of the variable.
1093
1094        Returns:
1095          a variable whose domain is [lb, ub].
1096        """
1097
1098        return self.new_var(lb, ub, False, name)

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

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

a variable whose domain is [lb, ub].

def new_bool_var( self, name: Optional[str] = None) -> Variable:
1100    def new_bool_var(self, name: Optional[str] = None) -> Variable:
1101        """Creates a 0-1 variable with the given name."""
1102        return self.new_var(
1103            0, 1, True, name
1104        )  # pytype: disable=wrong-arg-types  # numpy-scalars

Creates a 0-1 variable with the given name.

def new_constant( self, value: Union[int, float, numbers.Real, numpy.number]) -> Variable:
1106    def new_constant(self, value: NumberT) -> Variable:
1107        """Declares a constant variable."""
1108        return self.new_var(value, value, False, None)

Declares a constant variable.

def new_var_series( self, name: str, index: pandas.core.indexes.base.Index, lower_bounds: Union[int, float, numbers.Real, numpy.number, pandas.core.series.Series] = -inf, upper_bounds: Union[int, float, numbers.Real, numpy.number, pandas.core.series.Series] = inf, is_integral: Union[bool, pandas.core.series.Series] = False) -> pandas.core.series.Series:
1110    def new_var_series(
1111        self,
1112        name: str,
1113        index: pd.Index,
1114        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1115        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1116        is_integral: Union[bool, pd.Series] = False,
1117    ) -> pd.Series:
1118        """Creates a series of (scalar-valued) variables with the given name.
1119
1120        Args:
1121          name (str): Required. The name of the variable set.
1122          index (pd.Index): Required. The index to use for the variable set.
1123          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1124            variables in the set. If a `pd.Series` is passed in, it will be based on
1125            the corresponding values of the pd.Series. Defaults to -inf.
1126          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1127            variables in the set. If a `pd.Series` is passed in, it will be based on
1128            the corresponding values of the pd.Series. Defaults to +inf.
1129          is_integral (bool, pd.Series): Optional. Indicates if the variable can
1130            only take integer values. If a `pd.Series` is passed in, it will be
1131            based on the corresponding values of the pd.Series. Defaults to False.
1132
1133        Returns:
1134          pd.Series: The variable set indexed by its corresponding dimensions.
1135
1136        Raises:
1137          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1138          ValueError: if the `name` is not a valid identifier or already exists.
1139          ValueError: if the `lowerbound` is greater than the `upperbound`.
1140          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1141          does not match the input index.
1142        """
1143        if not isinstance(index, pd.Index):
1144            raise TypeError("Non-index object is used as index")
1145        if not name.isidentifier():
1146            raise ValueError("name={} is not a valid identifier".format(name))
1147        if (
1148            mbn.is_a_number(lower_bounds)
1149            and mbn.is_a_number(upper_bounds)
1150            and lower_bounds > upper_bounds
1151        ):
1152            raise ValueError(
1153                "lower_bound={} is greater than upper_bound={} for variable set={}".format(
1154                    lower_bounds, upper_bounds, name
1155                )
1156            )
1157        if (
1158            isinstance(is_integral, bool)
1159            and is_integral
1160            and mbn.is_a_number(lower_bounds)
1161            and mbn.is_a_number(upper_bounds)
1162            and math.isfinite(lower_bounds)
1163            and math.isfinite(upper_bounds)
1164            and math.ceil(lower_bounds) > math.floor(upper_bounds)
1165        ):
1166            raise ValueError(
1167                "ceil(lower_bound={})={}".format(lower_bounds, math.ceil(lower_bounds))
1168                + " is greater than floor("
1169                + "upper_bound={})={}".format(upper_bounds, math.floor(upper_bounds))
1170                + " for variable set={}".format(name)
1171            )
1172        lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index)
1173        upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index)
1174        is_integrals = _convert_to_series_and_validate_index(is_integral, index)
1175        return pd.Series(
1176            index=index,
1177            data=[
1178                # pylint: disable=g-complex-comprehension
1179                Variable(
1180                    helper=self.__helper,
1181                    name=f"{name}[{i}]",
1182                    lb=lower_bounds[i],
1183                    ub=upper_bounds[i],
1184                    is_integral=is_integrals[i],
1185                )
1186                for i in index
1187            ],
1188        )

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, float, pd.Series]): Optional. A lower bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series. Defaults to -inf.
  • upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series. Defaults to +inf.
  • is_integral (bool, pd.Series): Optional. Indicates if the variable can only take integer values. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series. Defaults to False.
Returns:

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

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the lowerbound is greater than the upperbound.
  • ValueError: if the index of lower_bound, upper_bound, or is_integer
  • does not match the input index.
def new_num_var_series( self, name: str, index: pandas.core.indexes.base.Index, lower_bounds: Union[int, float, numbers.Real, numpy.number, pandas.core.series.Series] = -inf, upper_bounds: Union[int, float, numbers.Real, numpy.number, pandas.core.series.Series] = inf) -> pandas.core.series.Series:
1190    def new_num_var_series(
1191        self,
1192        name: str,
1193        index: pd.Index,
1194        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1195        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1196    ) -> pd.Series:
1197        """Creates a series of continuous variables with the given name.
1198
1199        Args:
1200          name (str): Required. The name of the variable set.
1201          index (pd.Index): Required. The index to use for the variable set.
1202          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1203            variables in the set. If a `pd.Series` is passed in, it will be based on
1204            the corresponding values of the pd.Series. Defaults to -inf.
1205          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1206            variables in the set. If a `pd.Series` is passed in, it will be based on
1207            the corresponding values of the pd.Series. Defaults to +inf.
1208
1209        Returns:
1210          pd.Series: The variable set indexed by its corresponding 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 `lowerbound` is greater than the `upperbound`.
1216          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1217          does not match the input index.
1218        """
1219        return self.new_var_series(name, index, lower_bounds, upper_bounds, False)

Creates a series of continuous 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, float, pd.Series]): Optional. A lower bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series. Defaults to -inf.
  • upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series. Defaults to +inf.
Returns:

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

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the lowerbound is greater than the upperbound.
  • ValueError: if the index of lower_bound, upper_bound, or is_integer
  • does not match the input index.
def new_int_var_series( self, name: str, index: pandas.core.indexes.base.Index, lower_bounds: Union[int, float, numbers.Real, numpy.number, pandas.core.series.Series] = -inf, upper_bounds: Union[int, float, numbers.Real, numpy.number, pandas.core.series.Series] = inf) -> pandas.core.series.Series:
1221    def new_int_var_series(
1222        self,
1223        name: str,
1224        index: pd.Index,
1225        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
1226        upper_bounds: Union[NumberT, pd.Series] = math.inf,
1227    ) -> pd.Series:
1228        """Creates a series of integer variables with the given name.
1229
1230        Args:
1231          name (str): Required. The name of the variable set.
1232          index (pd.Index): Required. The index to use for the variable set.
1233          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
1234            variables in the set. If a `pd.Series` is passed in, it will be based on
1235            the corresponding values of the pd.Series. Defaults to -inf.
1236          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
1237            variables in the set. If a `pd.Series` is passed in, it will be based on
1238            the corresponding values of the pd.Series. Defaults to +inf.
1239
1240        Returns:
1241          pd.Series: The variable set indexed by its corresponding dimensions.
1242
1243        Raises:
1244          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1245          ValueError: if the `name` is not a valid identifier or already exists.
1246          ValueError: if the `lowerbound` is greater than the `upperbound`.
1247          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1248          does not match the input index.
1249        """
1250        return self.new_var_series(name, index, lower_bounds, upper_bounds, True)

Creates a series of integer 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, float, pd.Series]): Optional. A lower bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series. Defaults to -inf.
  • upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for variables in the set. If a pd.Series is passed in, it will be based on the corresponding values of the pd.Series. Defaults to +inf.
Returns:

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

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the lowerbound is greater than the upperbound.
  • ValueError: if the index of lower_bound, upper_bound, or is_integer
  • does not match the input index.
def new_bool_var_series( self, name: str, index: pandas.core.indexes.base.Index) -> pandas.core.series.Series:
1252    def new_bool_var_series(
1253        self,
1254        name: str,
1255        index: pd.Index,
1256    ) -> pd.Series:
1257        """Creates a series of Boolean variables with the given name.
1258
1259        Args:
1260          name (str): Required. The name of the variable set.
1261          index (pd.Index): Required. The index to use for the variable set.
1262
1263        Returns:
1264          pd.Series: The variable set indexed by its corresponding dimensions.
1265
1266        Raises:
1267          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
1268          ValueError: if the `name` is not a valid identifier or already exists.
1269          ValueError: if the `lowerbound` is greater than the `upperbound`.
1270          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
1271          does not match the input index.
1272        """
1273        return self.new_var_series(name, index, 0, 1, True)

Creates a series of Boolean variables with the given name.

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

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

Raises:
  • TypeError: if the index is invalid (e.g. a DataFrame).
  • ValueError: if the name is not a valid identifier or already exists.
  • ValueError: if the lowerbound is greater than the upperbound.
  • ValueError: if the index of lower_bound, upper_bound, or is_integer
  • does not match the input index.
def var_from_index( self, index: Union[int, numbers.Integral, numpy.integer]) -> Variable:
1275    def var_from_index(self, index: IntegerT) -> Variable:
1276        """Rebuilds a variable object from the model and its index."""
1277        return Variable(self.__helper, index, None, None, None)

Rebuilds a variable object from the model and its index.

def add_linear_constraint( self, linear_expr: Union[LinearExpr, int, float, numbers.Real, numpy.number], lb: Union[int, float, numbers.Real, numpy.number] = -inf, ub: Union[int, float, numbers.Real, numpy.number] = inf, name: Optional[str] = None) -> LinearConstraint:
1281    def add_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
1282        self,
1283        linear_expr: LinearExprT,
1284        lb: NumberT = -math.inf,
1285        ub: NumberT = math.inf,
1286        name: Optional[str] = None,
1287    ) -> LinearConstraint:
1288        """Adds the constraint: `lb <= linear_expr <= ub` with the given name."""
1289        ct = LinearConstraint(self.__helper)
1290        if name:
1291            self.__helper.set_constraint_name(ct.index, name)
1292        if mbn.is_a_number(linear_expr):
1293            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
1294            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
1295        elif isinstance(linear_expr, Variable):
1296            self.__helper.set_constraint_lower_bound(ct.index, lb)
1297            self.__helper.set_constraint_upper_bound(ct.index, ub)
1298            self.__helper.add_term_to_constraint(ct.index, linear_expr.index, 1.0)
1299        elif isinstance(linear_expr, LinearExpr):
1300            flat_expr = _as_flat_linear_expression(linear_expr)
1301            # pylint: disable=protected-access
1302            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr._offset)
1303            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr._offset)
1304            self.__helper.add_terms_to_constraint(
1305                ct.index, flat_expr._variable_indices, flat_expr._coefficients
1306            )
1307        else:
1308            raise TypeError(
1309                f"Not supported: Model.add_linear_constraint({linear_expr})"
1310                f" with type {type(linear_expr)}"
1311            )
1312        return ct

Adds the constraint: lb <= linear_expr <= ub with the given name.

def add( self, ct: Union[ortools.linear_solver.python.model_builder._BoundedLinearExpr, bool, pandas.core.series.Series], name: Optional[str] = None) -> Union[LinearConstraint, pandas.core.series.Series]:
1314    def add(
1315        self, ct: Union[ConstraintT, pd.Series], name: Optional[str] = None
1316    ) -> Union[LinearConstraint, pd.Series]:
1317        """Adds a `BoundedLinearExpression` to the model.
1318
1319        Args:
1320          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
1321          name: An optional name.
1322
1323        Returns:
1324          An instance of the `Constraint` class.
1325
1326        Note that a special treatment is done when the argument does not contain any
1327        variable, and thus evaluates to True or False.
1328
1329        model.add(True) will create a constraint 0 <= empty sum <= 0
1330
1331        model.add(False) will create a constraint inf <= empty sum <= -inf
1332
1333        you can check the if a constraint is always false (lb=inf, ub=-inf) by
1334        calling LinearConstraint.is_always_false()
1335        """
1336        if isinstance(ct, _BoundedLinearExpr):
1337            return ct._add_linear_constraint(self.__helper, name)
1338        elif isinstance(ct, bool):
1339            return _add_linear_constraint_to_helper(ct, self.__helper, name)
1340        elif isinstance(ct, pd.Series):
1341            return pd.Series(
1342                index=ct.index,
1343                data=[
1344                    _add_linear_constraint_to_helper(
1345                        expr, self.__helper, f"{name}[{i}]"
1346                    )
1347                    for (i, expr) in zip(ct.index, ct)
1348                ],
1349            )
1350        else:
1351            raise TypeError("Not supported: Model.add(" + str(ct) + ")")

Adds a BoundedLinearExpression to the model.

Arguments:
Returns:

An instance of the Constraint class.

Note that a special treatment is done when the argument does not contain any variable, and thus evaluates to True or False.

model.add(True) will create a constraint 0 <= empty sum <= 0

model.add(False) will create a constraint inf <= empty sum <= -inf

you can check the if a constraint is always false (lb=inf, ub=-inf) by calling LinearConstraint.is_always_false()

def linear_constraint_from_index( self, index: Union[int, numbers.Integral, numpy.integer]) -> LinearConstraint:
1353    def linear_constraint_from_index(self, index: IntegerT) -> LinearConstraint:
1354        """Rebuilds a linear constraint object from the model and its index."""
1355        return LinearConstraint(self.__helper, index)

Rebuilds a linear constraint object from the model and its index.

def add_enforced_linear_constraint( self, linear_expr: Union[LinearExpr, int, float, numbers.Real, numpy.number], ivar: Variable, ivalue: bool, lb: Union[int, float, numbers.Real, numpy.number] = -inf, ub: Union[int, float, numbers.Real, numpy.number] = inf, name: Optional[str] = None) -> EnforcedLinearConstraint:
1359    def add_enforced_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
1360        self,
1361        linear_expr: LinearExprT,
1362        ivar: "Variable",
1363        ivalue: bool,
1364        lb: NumberT = -math.inf,
1365        ub: NumberT = math.inf,
1366        name: Optional[str] = None,
1367    ) -> EnforcedLinearConstraint:
1368        """Adds the constraint: `ivar == ivalue => lb <= linear_expr <= ub` with the given name."""
1369        ct = EnforcedLinearConstraint(self.__helper)
1370        ct.indicator_variable = ivar
1371        ct.indicator_value = ivalue
1372        if name:
1373            self.__helper.set_constraint_name(ct.index, name)
1374        if mbn.is_a_number(linear_expr):
1375            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
1376            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
1377        elif isinstance(linear_expr, Variable):
1378            self.__helper.set_constraint_lower_bound(ct.index, lb)
1379            self.__helper.set_constraint_upper_bound(ct.index, ub)
1380            self.__helper.add_term_to_constraint(ct.index, linear_expr.index, 1.0)
1381        elif isinstance(linear_expr, LinearExpr):
1382            flat_expr = _as_flat_linear_expression(linear_expr)
1383            # pylint: disable=protected-access
1384            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr._offset)
1385            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr._offset)
1386            self.__helper.add_terms_to_constraint(
1387                ct.index, flat_expr._variable_indices, flat_expr._coefficients
1388            )
1389        else:
1390            raise TypeError(
1391                "Not supported:"
1392                f" Model.add_enforced_linear_constraint({linear_expr}) with"
1393                f" type {type(linear_expr)}"
1394            )
1395        return ct

Adds the constraint: ivar == ivalue => lb <= linear_expr <= ub with the given name.

def add_enforced( self, ct: Union[ortools.linear_solver.python.model_builder._BoundedLinearExpr, bool, pandas.core.series.Series], var: Union[Variable, pandas.core.series.Series], value: Union[bool, pandas.core.series.Series], name: Optional[str] = None) -> Union[EnforcedLinearConstraint, pandas.core.series.Series]:
1397    def add_enforced(
1398        self,
1399        ct: Union[ConstraintT, pd.Series],
1400        var: Union[Variable, pd.Series],
1401        value: Union[bool, pd.Series],
1402        name: Optional[str] = None,
1403    ) -> Union[EnforcedLinearConstraint, pd.Series]:
1404        """Adds a `ivar == ivalue => BoundedLinearExpression` to the model.
1405
1406        Args:
1407          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
1408          var: The indicator variable
1409          value: the indicator value
1410          name: An optional name.
1411
1412        Returns:
1413          An instance of the `Constraint` class.
1414
1415        Note that a special treatment is done when the argument does not contain any
1416        variable, and thus evaluates to True or False.
1417
1418        model.add_enforced(True, ivar, ivalue) will create a constraint 0 <= empty
1419        sum <= 0
1420
1421        model.add_enforced(False, var, value) will create a constraint inf <=
1422        empty sum <= -inf
1423
1424        you can check the if a constraint is always false (lb=inf, ub=-inf) by
1425        calling EnforcedLinearConstraint.is_always_false()
1426        """
1427        if isinstance(ct, _BoundedLinearExpr):
1428            return ct._add_enforced_linear_constraint(self.__helper, var, value, name)
1429        elif (
1430            isinstance(ct, bool)
1431            and isinstance(var, Variable)
1432            and isinstance(value, bool)
1433        ):
1434            return _add_enforced_linear_constraint_to_helper(
1435                ct, self.__helper, var, value, name
1436            )
1437        elif isinstance(ct, pd.Series):
1438            ivar_series = _convert_to_var_series_and_validate_index(var, ct.index)
1439            ivalue_series = _convert_to_series_and_validate_index(value, ct.index)
1440            return pd.Series(
1441                index=ct.index,
1442                data=[
1443                    _add_enforced_linear_constraint_to_helper(
1444                        expr,
1445                        self.__helper,
1446                        ivar_series[i],
1447                        ivalue_series[i],
1448                        f"{name}[{i}]",
1449                    )
1450                    for (i, expr) in zip(ct.index, ct)
1451                ],
1452            )
1453        else:
1454            raise TypeError("Not supported: Model.add_enforced(" + str(ct) + ")")

Adds a ivar == ivalue => BoundedLinearExpression to the model.

Arguments:
Returns:

An instance of the Constraint class.

Note that a special treatment is done when the argument does not contain any variable, and thus evaluates to True or False.

model.add_enforced(True, ivar, ivalue) will create a constraint 0 <= empty sum <= 0

model.add_enforced(False, var, value) will create a constraint inf <= empty sum <= -inf

you can check the if a constraint is always false (lb=inf, ub=-inf) by calling EnforcedLinearConstraint.is_always_false()

def enforced_linear_constraint_from_index( self, index: Union[int, numbers.Integral, numpy.integer]) -> EnforcedLinearConstraint:
1456    def enforced_linear_constraint_from_index(
1457        self, index: IntegerT
1458    ) -> EnforcedLinearConstraint:
1459        """Rebuilds an enforced linear constraint object from the model and its index."""
1460        return EnforcedLinearConstraint(self.__helper, index)

Rebuilds an enforced linear constraint object from the model and its index.

def minimize( self, linear_expr: Union[LinearExpr, int, float, numbers.Real, numpy.number]) -> None:
1463    def minimize(self, linear_expr: LinearExprT) -> None:
1464        """Minimizes the given objective."""
1465        self.__optimize(linear_expr, False)

Minimizes the given objective.

def maximize( self, linear_expr: Union[LinearExpr, int, float, numbers.Real, numpy.number]) -> None:
1467    def maximize(self, linear_expr: LinearExprT) -> None:
1468        """Maximizes the given objective."""
1469        self.__optimize(linear_expr, True)

Maximizes the given objective.

objective_offset: numpy.float64
1489    @property
1490    def objective_offset(self) -> np.double:
1491        """Returns the fixed offset of the objective."""
1492        return self.__helper.objective_offset()

Returns the fixed offset of the objective.

def objective_expression(self) -> ortools.linear_solver.python.model_builder._LinearExpression:
1498    def objective_expression(self) -> "_LinearExpression":
1499        """Returns the expression to optimize."""
1500        return _as_flat_linear_expression(
1501            sum(
1502                variable * self.__helper.var_objective_coefficient(variable.index)
1503                for variable in self.get_variables()
1504                if self.__helper.var_objective_coefficient(variable.index) != 0.0
1505            )
1506            + self.__helper.objective_offset()
1507        )

Returns the expression to optimize.

def clear_hints(self):
1510    def clear_hints(self):
1511        """Clears all solution hints."""
1512        self.__helper.clear_hints()

Clears all solution hints.

def add_hint( self, var: Variable, value: Union[int, float, numbers.Real, numpy.number]) -> None:
1514    def add_hint(self, var: Variable, value: NumberT) -> None:
1515        """Adds var == value as a hint to the model.
1516
1517        Args:
1518          var: The variable of the hint
1519          value: The value of the hint
1520
1521        Note that variables must not appear more than once in the list of hints.
1522        """
1523        self.__helper.add_hint(var.index, value)

Adds var == value as a hint to the model.

Arguments:
  • var: The variable of the hint
  • value: The value of the hint

Note that variables must not appear more than once in the list of hints.

def export_to_lp_string(self, obfuscate: bool = False) -> str:
1526    def export_to_lp_string(self, obfuscate: bool = False) -> str:
1527        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1528        options.obfuscate = obfuscate
1529        return self.__helper.export_to_lp_string(options)
def export_to_mps_string(self, obfuscate: bool = False) -> str:
1531    def export_to_mps_string(self, obfuscate: bool = False) -> str:
1532        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1533        options.obfuscate = obfuscate
1534        return self.__helper.export_to_mps_string(options)
def export_to_proto(self) -> ortools.linear_solver.linear_solver_pb2.MPModelProto:
1536    def export_to_proto(self) -> linear_solver_pb2.MPModelProto:
1537        """Exports the optimization model to a ProtoBuf format."""
1538        return mbh.to_mpmodel_proto(self.__helper)

Exports the optimization model to a ProtoBuf format.

def import_from_mps_string(self, mps_string: str) -> bool:
1540    def import_from_mps_string(self, mps_string: str) -> bool:
1541        """Reads a model from a MPS string."""
1542        return self.__helper.import_from_mps_string(mps_string)

Reads a model from a MPS string.

def import_from_mps_file(self, mps_file: str) -> bool:
1544    def import_from_mps_file(self, mps_file: str) -> bool:
1545        """Reads a model from a .mps file."""
1546        return self.__helper.import_from_mps_file(mps_file)

Reads a model from a .mps file.

def import_from_lp_string(self, lp_string: str) -> bool:
1548    def import_from_lp_string(self, lp_string: str) -> bool:
1549        """Reads a model from a LP string."""
1550        return self.__helper.import_from_lp_string(lp_string)

Reads a model from a LP string.

def import_from_lp_file(self, lp_file: str) -> bool:
1552    def import_from_lp_file(self, lp_file: str) -> bool:
1553        """Reads a model from a .lp file."""
1554        return self.__helper.import_from_lp_file(lp_file)

Reads a model from a .lp file.

def import_from_proto_file(self, proto_file: str) -> bool:
1556    def import_from_proto_file(self, proto_file: str) -> bool:
1557        """Reads a model from a proto file."""
1558        return self.__helper.read_model_from_proto_file(proto_file)

Reads a model from a proto file.

def export_to_proto_file(self, proto_file: str) -> bool:
1560    def export_to_proto_file(self, proto_file: str) -> bool:
1561        """Writes a model to a proto file."""
1562        return self.__helper.write_model_to_proto_file(proto_file)

Writes a model to a proto file.

num_variables: int
1566    @property
1567    def num_variables(self) -> int:
1568        """Returns the number of variables in the model."""
1569        return self.__helper.num_variables()

Returns the number of variables in the model.

num_constraints: int
1571    @property
1572    def num_constraints(self) -> int:
1573        """The number of constraints in the model."""
1574        return self.__helper.num_constraints()

The number of constraints in the model.

name: str
1576    @property
1577    def name(self) -> str:
1578        """The name of the model."""
1579        return self.__helper.name()

The name of the model.

1585    @property
1586    def helper(self) -> mbh.ModelBuilderHelper:
1587        """Returns the model builder helper."""
1588        return self.__helper

Returns the model builder helper.

class Solver:
1591class Solver:
1592    """Main solver class.
1593
1594    The purpose of this class is to search for a solution to the model provided
1595    to the solve() method.
1596
1597    Once solve() is called, this class allows inspecting the solution found
1598    with the value() method, as well as general statistics about the solve
1599    procedure.
1600    """
1601
1602    def __init__(self, solver_name: str):
1603        self.__solve_helper: mbh.ModelSolverHelper = mbh.ModelSolverHelper(solver_name)
1604        self.log_callback: Optional[Callable[[str], None]] = None
1605
1606    def solver_is_supported(self) -> bool:
1607        """Checks whether the requested solver backend was found."""
1608        return self.__solve_helper.solver_is_supported()
1609
1610    # Solver backend and parameters.
1611    def set_time_limit_in_seconds(self, limit: NumberT) -> None:
1612        """Sets a time limit for the solve() call."""
1613        self.__solve_helper.set_time_limit_in_seconds(limit)
1614
1615    def set_solver_specific_parameters(self, parameters: str) -> None:
1616        """Sets parameters specific to the solver backend."""
1617        self.__solve_helper.set_solver_specific_parameters(parameters)
1618
1619    def enable_output(self, enabled: bool) -> None:
1620        """Controls the solver backend logs."""
1621        self.__solve_helper.enable_output(enabled)
1622
1623    def solve(self, model: Model) -> SolveStatus:
1624        """Solves a problem and passes each solution to the callback if not null."""
1625        if self.log_callback is not None:
1626            self.__solve_helper.set_log_callback(self.log_callback)
1627        else:
1628            self.__solve_helper.clear_log_callback()
1629        self.__solve_helper.solve(model.helper)
1630        return SolveStatus(self.__solve_helper.status())
1631
1632    def stop_search(self):
1633        """Stops the current search asynchronously."""
1634        self.__solve_helper.interrupt_solve()
1635
1636    def value(self, expr: LinearExprT) -> np.double:
1637        """Returns the value of a linear expression after solve."""
1638        if not self.__solve_helper.has_solution():
1639            return pd.NA
1640        if mbn.is_a_number(expr):
1641            return expr
1642        elif isinstance(expr, Variable):
1643            return self.__solve_helper.var_value(expr.index)
1644        elif isinstance(expr, LinearExpr):
1645            flat_expr = _as_flat_linear_expression(expr)
1646            return self.__solve_helper.expression_value(
1647                flat_expr._variable_indices,
1648                flat_expr._coefficients,
1649                flat_expr._offset,
1650            )
1651        else:
1652            raise TypeError(f"Unknown expression {expr!r} of type {type(expr)}")
1653
1654    def values(self, variables: _IndexOrSeries) -> pd.Series:
1655        """Returns the values of the input variables.
1656
1657        If `variables` is a `pd.Index`, then the output will be indexed by the
1658        variables. If `variables` is a `pd.Series` indexed by the underlying
1659        dimensions, then the output will be indexed by the same underlying
1660        dimensions.
1661
1662        Args:
1663          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1664            get the values.
1665
1666        Returns:
1667          pd.Series: The values of all variables in the set.
1668        """
1669        if not self.__solve_helper.has_solution():
1670            return _attribute_series(func=lambda v: pd.NA, values=variables)
1671        return _attribute_series(
1672            func=lambda v: self.__solve_helper.var_value(v.index),
1673            values=variables,
1674        )
1675
1676    def reduced_costs(self, variables: _IndexOrSeries) -> pd.Series:
1677        """Returns the reduced cost of the input variables.
1678
1679        If `variables` is a `pd.Index`, then the output will be indexed by the
1680        variables. If `variables` is a `pd.Series` indexed by the underlying
1681        dimensions, then the output will be indexed by the same underlying
1682        dimensions.
1683
1684        Args:
1685          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1686            get the values.
1687
1688        Returns:
1689          pd.Series: The reduced cost of all variables in the set.
1690        """
1691        if not self.__solve_helper.has_solution():
1692            return _attribute_series(func=lambda v: pd.NA, values=variables)
1693        return _attribute_series(
1694            func=lambda v: self.__solve_helper.reduced_cost(v.index),
1695            values=variables,
1696        )
1697
1698    def reduced_cost(self, var: Variable) -> np.double:
1699        """Returns the reduced cost of a linear expression after solve."""
1700        if not self.__solve_helper.has_solution():
1701            return pd.NA
1702        return self.__solve_helper.reduced_cost(var.index)
1703
1704    def dual_values(self, constraints: _IndexOrSeries) -> pd.Series:
1705        """Returns the dual values of the input constraints.
1706
1707        If `constraints` is a `pd.Index`, then the output will be indexed by the
1708        constraints. If `constraints` is a `pd.Series` indexed by the underlying
1709        dimensions, then the output will be indexed by the same underlying
1710        dimensions.
1711
1712        Args:
1713          constraints (Union[pd.Index, pd.Series]): The set of constraints from
1714            which to get the dual values.
1715
1716        Returns:
1717          pd.Series: The dual_values of all constraints in the set.
1718        """
1719        if not self.__solve_helper.has_solution():
1720            return _attribute_series(func=lambda v: pd.NA, values=constraints)
1721        return _attribute_series(
1722            func=lambda v: self.__solve_helper.dual_value(v.index),
1723            values=constraints,
1724        )
1725
1726    def dual_value(self, ct: LinearConstraint) -> np.double:
1727        """Returns the dual value of a linear constraint after solve."""
1728        if not self.__solve_helper.has_solution():
1729            return pd.NA
1730        return self.__solve_helper.dual_value(ct.index)
1731
1732    def activity(self, ct: LinearConstraint) -> np.double:
1733        """Returns the activity of a linear constraint after solve."""
1734        if not self.__solve_helper.has_solution():
1735            return pd.NA
1736        return self.__solve_helper.activity(ct.index)
1737
1738    @property
1739    def objective_value(self) -> np.double:
1740        """Returns the value of the objective after solve."""
1741        if not self.__solve_helper.has_solution():
1742            return pd.NA
1743        return self.__solve_helper.objective_value()
1744
1745    @property
1746    def best_objective_bound(self) -> np.double:
1747        """Returns the best lower (upper) bound found when min(max)imizing."""
1748        if not self.__solve_helper.has_solution():
1749            return pd.NA
1750        return self.__solve_helper.best_objective_bound()
1751
1752    @property
1753    def status_string(self) -> str:
1754        """Returns additional information of the last solve.
1755
1756        It can describe why the model is invalid.
1757        """
1758        return self.__solve_helper.status_string()
1759
1760    @property
1761    def wall_time(self) -> np.double:
1762        return self.__solve_helper.wall_time()
1763
1764    @property
1765    def user_time(self) -> np.double:
1766        return self.__solve_helper.user_time()

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() method, as well as general statistics about the solve procedure.

Solver(solver_name: str)
1602    def __init__(self, solver_name: str):
1603        self.__solve_helper: mbh.ModelSolverHelper = mbh.ModelSolverHelper(solver_name)
1604        self.log_callback: Optional[Callable[[str], None]] = None
log_callback: Optional[Callable[[str], NoneType]]
def solver_is_supported(self) -> bool:
1606    def solver_is_supported(self) -> bool:
1607        """Checks whether the requested solver backend was found."""
1608        return self.__solve_helper.solver_is_supported()

Checks whether the requested solver backend was found.

def set_time_limit_in_seconds(self, limit: Union[int, float, numbers.Real, numpy.number]) -> None:
1611    def set_time_limit_in_seconds(self, limit: NumberT) -> None:
1612        """Sets a time limit for the solve() call."""
1613        self.__solve_helper.set_time_limit_in_seconds(limit)

Sets a time limit for the solve() call.

def set_solver_specific_parameters(self, parameters: str) -> None:
1615    def set_solver_specific_parameters(self, parameters: str) -> None:
1616        """Sets parameters specific to the solver backend."""
1617        self.__solve_helper.set_solver_specific_parameters(parameters)

Sets parameters specific to the solver backend.

def enable_output(self, enabled: bool) -> None:
1619    def enable_output(self, enabled: bool) -> None:
1620        """Controls the solver backend logs."""
1621        self.__solve_helper.enable_output(enabled)

Controls the solver backend logs.

def solve( self, model: Model) -> SolveStatus:
1623    def solve(self, model: Model) -> SolveStatus:
1624        """Solves a problem and passes each solution to the callback if not null."""
1625        if self.log_callback is not None:
1626            self.__solve_helper.set_log_callback(self.log_callback)
1627        else:
1628            self.__solve_helper.clear_log_callback()
1629        self.__solve_helper.solve(model.helper)
1630        return SolveStatus(self.__solve_helper.status())

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

def value( self, expr: Union[LinearExpr, int, float, numbers.Real, numpy.number]) -> numpy.float64:
1636    def value(self, expr: LinearExprT) -> np.double:
1637        """Returns the value of a linear expression after solve."""
1638        if not self.__solve_helper.has_solution():
1639            return pd.NA
1640        if mbn.is_a_number(expr):
1641            return expr
1642        elif isinstance(expr, Variable):
1643            return self.__solve_helper.var_value(expr.index)
1644        elif isinstance(expr, LinearExpr):
1645            flat_expr = _as_flat_linear_expression(expr)
1646            return self.__solve_helper.expression_value(
1647                flat_expr._variable_indices,
1648                flat_expr._coefficients,
1649                flat_expr._offset,
1650            )
1651        else:
1652            raise TypeError(f"Unknown expression {expr!r} of type {type(expr)}")

Returns the value of a linear expression after solve.

def values( self, variables: Union[pandas.core.indexes.base.Index, pandas.core.series.Series]) -> pandas.core.series.Series:
1654    def values(self, variables: _IndexOrSeries) -> pd.Series:
1655        """Returns the values of the input variables.
1656
1657        If `variables` is a `pd.Index`, then the output will be indexed by the
1658        variables. If `variables` is a `pd.Series` indexed by the underlying
1659        dimensions, then the output will be indexed by the same underlying
1660        dimensions.
1661
1662        Args:
1663          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1664            get the values.
1665
1666        Returns:
1667          pd.Series: The values of all variables in the set.
1668        """
1669        if not self.__solve_helper.has_solution():
1670            return _attribute_series(func=lambda v: pd.NA, values=variables)
1671        return _attribute_series(
1672            func=lambda v: self.__solve_helper.var_value(v.index),
1673            values=variables,
1674        )

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.

def reduced_costs( self, variables: Union[pandas.core.indexes.base.Index, pandas.core.series.Series]) -> pandas.core.series.Series:
1676    def reduced_costs(self, variables: _IndexOrSeries) -> pd.Series:
1677        """Returns the reduced cost of the input variables.
1678
1679        If `variables` is a `pd.Index`, then the output will be indexed by the
1680        variables. If `variables` is a `pd.Series` indexed by the underlying
1681        dimensions, then the output will be indexed by the same underlying
1682        dimensions.
1683
1684        Args:
1685          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1686            get the values.
1687
1688        Returns:
1689          pd.Series: The reduced cost of all variables in the set.
1690        """
1691        if not self.__solve_helper.has_solution():
1692            return _attribute_series(func=lambda v: pd.NA, values=variables)
1693        return _attribute_series(
1694            func=lambda v: self.__solve_helper.reduced_cost(v.index),
1695            values=variables,
1696        )

Returns the reduced cost 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 reduced cost of all variables in the set.

def reduced_cost( self, var: Variable) -> numpy.float64:
1698    def reduced_cost(self, var: Variable) -> np.double:
1699        """Returns the reduced cost of a linear expression after solve."""
1700        if not self.__solve_helper.has_solution():
1701            return pd.NA
1702        return self.__solve_helper.reduced_cost(var.index)

Returns the reduced cost of a linear expression after solve.

def dual_values( self, constraints: Union[pandas.core.indexes.base.Index, pandas.core.series.Series]) -> pandas.core.series.Series:
1704    def dual_values(self, constraints: _IndexOrSeries) -> pd.Series:
1705        """Returns the dual values of the input constraints.
1706
1707        If `constraints` is a `pd.Index`, then the output will be indexed by the
1708        constraints. If `constraints` is a `pd.Series` indexed by the underlying
1709        dimensions, then the output will be indexed by the same underlying
1710        dimensions.
1711
1712        Args:
1713          constraints (Union[pd.Index, pd.Series]): The set of constraints from
1714            which to get the dual values.
1715
1716        Returns:
1717          pd.Series: The dual_values of all constraints in the set.
1718        """
1719        if not self.__solve_helper.has_solution():
1720            return _attribute_series(func=lambda v: pd.NA, values=constraints)
1721        return _attribute_series(
1722            func=lambda v: self.__solve_helper.dual_value(v.index),
1723            values=constraints,
1724        )

Returns the dual values of the input constraints.

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

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

pd.Series: The dual_values of all constraints in the set.

def dual_value( self, ct: LinearConstraint) -> numpy.float64:
1726    def dual_value(self, ct: LinearConstraint) -> np.double:
1727        """Returns the dual value of a linear constraint after solve."""
1728        if not self.__solve_helper.has_solution():
1729            return pd.NA
1730        return self.__solve_helper.dual_value(ct.index)

Returns the dual value of a linear constraint after solve.

def activity( self, ct: LinearConstraint) -> numpy.float64:
1732    def activity(self, ct: LinearConstraint) -> np.double:
1733        """Returns the activity of a linear constraint after solve."""
1734        if not self.__solve_helper.has_solution():
1735            return pd.NA
1736        return self.__solve_helper.activity(ct.index)

Returns the activity of a linear constraint after solve.

objective_value: numpy.float64
1738    @property
1739    def objective_value(self) -> np.double:
1740        """Returns the value of the objective after solve."""
1741        if not self.__solve_helper.has_solution():
1742            return pd.NA
1743        return self.__solve_helper.objective_value()

Returns the value of the objective after solve.

best_objective_bound: numpy.float64
1745    @property
1746    def best_objective_bound(self) -> np.double:
1747        """Returns the best lower (upper) bound found when min(max)imizing."""
1748        if not self.__solve_helper.has_solution():
1749            return pd.NA
1750        return self.__solve_helper.best_objective_bound()

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

status_string: str
1752    @property
1753    def status_string(self) -> str:
1754        """Returns additional information of the last solve.
1755
1756        It can describe why the model is invalid.
1757        """
1758        return self.__solve_helper.status_string()

Returns additional information of the last solve.

It can describe why the model is invalid.

wall_time: numpy.float64
1760    @property
1761    def wall_time(self) -> np.double:
1762        return self.__solve_helper.wall_time()
user_time: numpy.float64
1764    @property
1765    def user_time(self) -> np.double:
1766        return self.__solve_helper.user_time()
ModelBuilder = <class 'Model'>
ModelSolver = <class 'Solver'>