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">Model: Methods for creating models, including variables and constraints.
  • .Solver">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">LinearExpr: Methods for creating constraints and the objective from large arrays of coefficients.

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

   1# Copyright 2010-2025 Google LLC
   2# Licensed under the Apache License, Version 2.0 (the "License");
   3# you may not use this file except in compliance with the License.
   4# You may obtain a copy of the License at
   5#
   6#     http://www.apache.org/licenses/LICENSE-2.0
   7#
   8# Unless required by applicable law or agreed to in writing, software
   9# distributed under the License is distributed on an "AS IS" BASIS,
  10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11# See the License for the specific language governing permissions and
  12# limitations under the License.
  13
  14"""Methods for building and solving 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 math
  36import numbers
  37import typing
  38from typing import Callable, Optional, Union
  39
  40import numpy as np
  41import pandas as pd
  42
  43from ortools.linear_solver import linear_solver_pb2
  44from ortools.linear_solver.python import model_builder_helper as mbh
  45from ortools.linear_solver.python import model_builder_numbers as mbn
  46
  47# Custom types.
  48NumberT = Union[int, float, numbers.Real, np.number]
  49IntegerT = Union[int, numbers.Integral, np.integer]
  50LinearExprT = Union[mbh.LinearExpr, NumberT]
  51ConstraintT = Union[mbh.BoundedLinearExpression, bool]
  52_IndexOrSeries = Union[pd.Index, pd.Series]
  53_VariableOrConstraint = Union["LinearConstraint", mbh.Variable]
  54
  55# Forward solve statuses.
  56AffineExpr = mbh.AffineExpr
  57BoundedLinearExpression = mbh.BoundedLinearExpression
  58FlatExpr = mbh.FlatExpr
  59LinearExpr = mbh.LinearExpr
  60SolveStatus = mbh.SolveStatus
  61Variable = mbh.Variable
  62
  63
  64def _add_linear_constraint_to_helper(
  65    bounded_expr: Union[bool, mbh.BoundedLinearExpression],
  66    helper: mbh.ModelBuilderHelper,
  67    name: Optional[str],
  68):
  69    """Creates a new linear constraint in the helper.
  70
  71    It handles boolean values (which might arise in the construction of
  72    BoundedLinearExpressions).
  73
  74    If bounded_expr is a Boolean value, the created constraint is different.
  75    In that case, the constraint will be immutable and marked as under-specified.
  76    It will be always feasible or infeasible whether the value is True or False.
  77
  78    Args:
  79      bounded_expr: The bounded expression used to create the constraint.
  80      helper: The helper to create the constraint.
  81      name: The name of the constraint to be created.
  82
  83    Returns:
  84      LinearConstraint: a constraint in the helper corresponding to the input.
  85
  86    Raises:
  87      TypeError: If constraint is an invalid type.
  88    """
  89    if isinstance(bounded_expr, bool):
  90        c = LinearConstraint(helper, is_under_specified=True)
  91        if name is not None:
  92            helper.set_constraint_name(c.index, name)
  93        if bounded_expr:
  94            # constraint that is always feasible: 0.0 <= nothing <= 0.0
  95            helper.set_constraint_lower_bound(c.index, 0.0)
  96            helper.set_constraint_upper_bound(c.index, 0.0)
  97        else:
  98            # constraint that is always infeasible: +oo <= nothing <= -oo
  99            helper.set_constraint_lower_bound(c.index, 1)
 100            helper.set_constraint_upper_bound(c.index, -1)
 101        return c
 102    if isinstance(bounded_expr, mbh.BoundedLinearExpression):
 103        c = LinearConstraint(helper)
 104        # pylint: disable=protected-access
 105        helper.add_terms_to_constraint(c.index, bounded_expr.vars, bounded_expr.coeffs)
 106        helper.set_constraint_lower_bound(c.index, bounded_expr.lower_bound)
 107        helper.set_constraint_upper_bound(c.index, bounded_expr.upper_bound)
 108        # pylint: enable=protected-access
 109        if name is not None:
 110            helper.set_constraint_name(c.index, name)
 111        return c
 112    raise TypeError(f"invalid type={type(bounded_expr).__name__!r}")
 113
 114
 115def _add_enforced_linear_constraint_to_helper(
 116    bounded_expr: Union[bool, mbh.BoundedLinearExpression],
 117    helper: mbh.ModelBuilderHelper,
 118    var: Variable,
 119    value: bool,
 120    name: Optional[str],
 121):
 122    """Creates a new enforced linear constraint in the helper.
 123
 124    It handles boolean values (which might arise in the construction of
 125    BoundedLinearExpressions).
 126
 127    If bounded_expr is a Boolean value, the linear part of the constraint is
 128    different.
 129    In that case, the constraint will be immutable and marked as under-specified.
 130    Its linear part will be always feasible or infeasible whether the value is
 131    True or False.
 132
 133    Args:
 134      bounded_expr: The bounded expression used to create the constraint.
 135      helper: The helper to create the constraint.
 136      var: the variable used in the indicator
 137      value: the value used in the indicator
 138      name: The name of the constraint to be created.
 139
 140    Returns:
 141      EnforcedLinearConstraint: a constraint in the helper corresponding to the
 142      input.
 143
 144    Raises:
 145      TypeError: If constraint is an invalid type.
 146    """
 147    if isinstance(bounded_expr, bool):
 148        # TODO(user): create indicator variable assignment instead ?
 149        c = EnforcedLinearConstraint(helper, is_under_specified=True)
 150        c.indicator_variable = var
 151        c.indicator_value = value
 152        if name is not None:
 153            helper.set_enforced_constraint_name(c.index, name)
 154        if bounded_expr:
 155            # constraint that is always feasible: 0.0 <= nothing <= 0.0
 156            helper.set_enforced_constraint_lower_bound(c.index, 0.0)
 157            helper.set_enforced_constraint_upper_bound(c.index, 0.0)
 158        else:
 159            # constraint that is always infeasible: +oo <= nothing <= -oo
 160            helper.set_enforced_constraint_lower_bound(c.index, 1)
 161            helper.set_enforced_constraint_upper_bound(c.index, -1)
 162        return c
 163    if isinstance(bounded_expr, mbh.BoundedLinearExpression):
 164        c = EnforcedLinearConstraint(helper)
 165        c.indicator_variable = var
 166        c.indicator_value = value
 167        helper.add_terms_to_enforced_constraint(
 168            c.index, bounded_expr.vars, bounded_expr.coeffs
 169        )
 170        helper.set_enforced_constraint_lower_bound(c.index, bounded_expr.lower_bound)
 171        helper.set_enforced_constraint_upper_bound(c.index, bounded_expr.upper_bound)
 172        if name is not None:
 173            helper.set_constraint_name(c.index, name)
 174        return c
 175
 176    raise TypeError(f"invalid type={type(bounded_expr).__name__!r}")
 177
 178
 179class LinearConstraint:
 180    """Stores a linear equation.
 181
 182    Example:
 183        x = model.new_num_var(0, 10, 'x')
 184        y = model.new_num_var(0, 10, 'y')
 185
 186        linear_constraint = model.add(x + 2 * y == 5)
 187    """
 188
 189    def __init__(
 190        self,
 191        helper: mbh.ModelBuilderHelper,
 192        *,
 193        index: Optional[IntegerT] = None,
 194        is_under_specified: bool = False,
 195    ) -> None:
 196        """LinearConstraint constructor.
 197
 198        Args:
 199          helper: The pybind11 ModelBuilderHelper.
 200          index: If specified, recreates a wrapper to an existing linear constraint.
 201          is_under_specified: indicates if the constraint was created by
 202            model.add(bool).
 203        """
 204        if index is None:
 205            self.__index = helper.add_linear_constraint()
 206        else:
 207            self.__index = index
 208        self.__helper: mbh.ModelBuilderHelper = helper
 209        self.__is_under_specified = is_under_specified
 210
 211    def __hash__(self):
 212        return hash((self.__helper, self.__index))
 213
 214    @property
 215    def index(self) -> IntegerT:
 216        """Returns the index of the constraint in the helper."""
 217        return self.__index
 218
 219    @property
 220    def helper(self) -> mbh.ModelBuilderHelper:
 221        """Returns the ModelBuilderHelper instance."""
 222        return self.__helper
 223
 224    @property
 225    def lower_bound(self) -> np.double:
 226        return self.__helper.constraint_lower_bound(self.__index)
 227
 228    @lower_bound.setter
 229    def lower_bound(self, bound: NumberT) -> None:
 230        self.assert_constraint_is_well_defined()
 231        self.__helper.set_constraint_lower_bound(self.__index, bound)
 232
 233    @property
 234    def upper_bound(self) -> np.double:
 235        return self.__helper.constraint_upper_bound(self.__index)
 236
 237    @upper_bound.setter
 238    def upper_bound(self, bound: NumberT) -> None:
 239        self.assert_constraint_is_well_defined()
 240        self.__helper.set_constraint_upper_bound(self.__index, bound)
 241
 242    @property
 243    def name(self) -> str:
 244        constraint_name = self.__helper.constraint_name(self.__index)
 245        if constraint_name:
 246            return constraint_name
 247        return f"linear_constraint#{self.__index}"
 248
 249    @name.setter
 250    def name(self, name: str) -> None:
 251        return self.__helper.set_constraint_name(self.__index, name)
 252
 253    @property
 254    def is_under_specified(self) -> bool:
 255        """Returns True if the constraint is under specified.
 256
 257        Usually, it means that it was created by model.add(False) or model.add(True)
 258        The effect is that modifying the constraint will raise an exception.
 259        """
 260        return self.__is_under_specified
 261
 262    def assert_constraint_is_well_defined(self) -> None:
 263        """Raises an exception if the constraint is under specified."""
 264        if self.__is_under_specified:
 265            raise ValueError(
 266                f"Constraint {self.index} is under specified and cannot be modified"
 267            )
 268
 269    def __str__(self):
 270        return self.name
 271
 272    def __repr__(self):
 273        return (
 274            f"LinearConstraint({self.name}, lb={self.lower_bound},"
 275            f" ub={self.upper_bound},"
 276            f" var_indices={self.helper.constraint_var_indices(self.index)},"
 277            f" coefficients={self.helper.constraint_coefficients(self.index)})"
 278        )
 279
 280    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
 281        """Sets the coefficient of the variable in the constraint."""
 282        self.assert_constraint_is_well_defined()
 283        self.__helper.set_constraint_coefficient(self.__index, var.index, coeff)
 284
 285    def add_term(self, var: Variable, coeff: NumberT) -> None:
 286        """Adds var * coeff to the constraint."""
 287        self.assert_constraint_is_well_defined()
 288        self.__helper.safe_add_term_to_constraint(self.__index, var.index, coeff)
 289
 290    def clear_terms(self) -> None:
 291        """Clear all terms of the constraint."""
 292        self.assert_constraint_is_well_defined()
 293        self.__helper.clear_constraint_terms(self.__index)
 294
 295
 296class EnforcedLinearConstraint:
 297    """Stores an enforced linear equation, also name indicator constraint.
 298
 299    Example:
 300        x = model.new_num_var(0, 10, 'x')
 301        y = model.new_num_var(0, 10, 'y')
 302        z = model.new_bool_var('z')
 303
 304        enforced_linear_constraint = model.add_enforced(x + 2 * y == 5, z, False)
 305    """
 306
 307    def __init__(
 308        self,
 309        helper: mbh.ModelBuilderHelper,
 310        *,
 311        index: Optional[IntegerT] = None,
 312        is_under_specified: bool = False,
 313    ) -> None:
 314        """EnforcedLinearConstraint constructor.
 315
 316        Args:
 317          helper: The pybind11 ModelBuilderHelper.
 318          index: If specified, recreates a wrapper to an existing linear constraint.
 319          is_under_specified: indicates if the constraint was created by
 320            model.add(bool).
 321        """
 322        if index is None:
 323            self.__index = helper.add_enforced_linear_constraint()
 324        else:
 325            if not helper.is_enforced_linear_constraint(index):
 326                raise ValueError(
 327                    f"the given index {index} does not refer to an enforced linear"
 328                    " constraint"
 329                )
 330
 331            self.__index = index
 332        self.__helper: mbh.ModelBuilderHelper = helper
 333        self.__is_under_specified = is_under_specified
 334
 335    @property
 336    def index(self) -> IntegerT:
 337        """Returns the index of the constraint in the helper."""
 338        return self.__index
 339
 340    @property
 341    def helper(self) -> mbh.ModelBuilderHelper:
 342        """Returns the ModelBuilderHelper instance."""
 343        return self.__helper
 344
 345    @property
 346    def lower_bound(self) -> np.double:
 347        return self.__helper.enforced_constraint_lower_bound(self.__index)
 348
 349    @lower_bound.setter
 350    def lower_bound(self, bound: NumberT) -> None:
 351        self.assert_constraint_is_well_defined()
 352        self.__helper.set_enforced_constraint_lower_bound(self.__index, bound)
 353
 354    @property
 355    def upper_bound(self) -> np.double:
 356        return self.__helper.enforced_constraint_upper_bound(self.__index)
 357
 358    @upper_bound.setter
 359    def upper_bound(self, bound: NumberT) -> None:
 360        self.assert_constraint_is_well_defined()
 361        self.__helper.set_enforced_constraint_upper_bound(self.__index, bound)
 362
 363    @property
 364    def indicator_variable(self) -> "Variable":
 365        enforcement_var_index = (
 366            self.__helper.enforced_constraint_indicator_variable_index(self.__index)
 367        )
 368        return Variable(self.__helper, enforcement_var_index)
 369
 370    @indicator_variable.setter
 371    def indicator_variable(self, var: "Variable") -> None:
 372        self.__helper.set_enforced_constraint_indicator_variable_index(
 373            self.__index, var.index
 374        )
 375
 376    @property
 377    def indicator_value(self) -> bool:
 378        return self.__helper.enforced_constraint_indicator_value(self.__index)
 379
 380    @indicator_value.setter
 381    def indicator_value(self, value: bool) -> None:
 382        self.__helper.set_enforced_constraint_indicator_value(self.__index, value)
 383
 384    @property
 385    def name(self) -> str:
 386        constraint_name = self.__helper.enforced_constraint_name(self.__index)
 387        if constraint_name:
 388            return constraint_name
 389        return f"enforced_linear_constraint#{self.__index}"
 390
 391    @name.setter
 392    def name(self, name: str) -> None:
 393        return self.__helper.set_enforced_constraint_name(self.__index, name)
 394
 395    @property
 396    def is_under_specified(self) -> bool:
 397        """Returns True if the constraint is under specified.
 398
 399        Usually, it means that it was created by model.add(False) or model.add(True)
 400        The effect is that modifying the constraint will raise an exception.
 401        """
 402        return self.__is_under_specified
 403
 404    def assert_constraint_is_well_defined(self) -> None:
 405        """Raises an exception if the constraint is under specified."""
 406        if self.__is_under_specified:
 407            raise ValueError(
 408                f"Constraint {self.index} is under specified and cannot be modified"
 409            )
 410
 411    def __str__(self):
 412        return self.name
 413
 414    def __repr__(self):
 415        return (
 416            f"EnforcedLinearConstraint({self.name}, lb={self.lower_bound},"
 417            f" ub={self.upper_bound},"
 418            f" var_indices={self.helper.enforced_constraint_var_indices(self.index)},"
 419            f" coefficients={self.helper.enforced_constraint_coefficients(self.index)},"
 420            f" indicator_variable={self.indicator_variable}"
 421            f" indicator_value={self.indicator_value})"
 422        )
 423
 424    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
 425        """Sets the coefficient of the variable in the constraint."""
 426        self.assert_constraint_is_well_defined()
 427        self.__helper.set_enforced_constraint_coefficient(
 428            self.__index, var.index, coeff
 429        )
 430
 431    def add_term(self, var: Variable, coeff: NumberT) -> None:
 432        """Adds var * coeff to the constraint."""
 433        self.assert_constraint_is_well_defined()
 434        self.__helper.safe_add_term_to_enforced_constraint(
 435            self.__index, var.index, coeff
 436        )
 437
 438    def clear_terms(self) -> None:
 439        """Clear all terms of the constraint."""
 440        self.assert_constraint_is_well_defined()
 441        self.__helper.clear_enforced_constraint_terms(self.__index)
 442
 443
 444class Model:
 445    """Methods for building a linear model.
 446
 447    Methods beginning with:
 448
 449    * ```new_``` create integer, boolean, or interval variables.
 450    * ```add_``` create new constraints and add them to the model.
 451    """
 452
 453    def __init__(self):
 454        self.__helper: mbh.ModelBuilderHelper = mbh.ModelBuilderHelper()
 455
 456    def clone(self) -> "Model":
 457        """Returns a clone of the current model."""
 458        clone = Model()
 459        clone.helper.overwrite_model(self.helper)
 460        return clone
 461
 462    @typing.overload
 463    def _get_linear_constraints(self, constraints: Optional[pd.Index]) -> pd.Index: ...
 464
 465    @typing.overload
 466    def _get_linear_constraints(self, constraints: pd.Series) -> pd.Series: ...
 467
 468    def _get_linear_constraints(
 469        self, constraints: Optional[_IndexOrSeries] = None
 470    ) -> _IndexOrSeries:
 471        if constraints is None:
 472            return self.get_linear_constraints()
 473        return constraints
 474
 475    @typing.overload
 476    def _get_variables(self, variables: Optional[pd.Index]) -> pd.Index: ...
 477
 478    @typing.overload
 479    def _get_variables(self, variables: pd.Series) -> pd.Series: ...
 480
 481    def _get_variables(
 482        self, variables: Optional[_IndexOrSeries] = None
 483    ) -> _IndexOrSeries:
 484        if variables is None:
 485            return self.get_variables()
 486        return variables
 487
 488    def get_linear_constraints(self) -> pd.Index:
 489        """Gets all linear constraints in the model."""
 490        return pd.Index(
 491            [self.linear_constraint_from_index(i) for i in range(self.num_constraints)],
 492            name="linear_constraint",
 493        )
 494
 495    def get_linear_constraint_expressions(
 496        self, constraints: Optional[_IndexOrSeries] = None
 497    ) -> pd.Series:
 498        """Gets the expressions of all linear constraints in the set.
 499
 500        If `constraints` is a `pd.Index`, then the output will be indexed by the
 501        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 502        dimensions, then the output will be indexed by the same underlying
 503        dimensions.
 504
 505        Args:
 506          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 507            constraints from which to get the expressions. If unspecified, all
 508            linear constraints will be in scope.
 509
 510        Returns:
 511          pd.Series: The expressions of all linear constraints in the set.
 512        """
 513        return _attribute_series(
 514            # pylint: disable=g-long-lambda
 515            func=lambda c: mbh.FlatExpr(
 516                # pylint: disable=g-complex-comprehension
 517                [
 518                    Variable(self.__helper, var_id)
 519                    for var_id in c.helper.constraint_var_indices(c.index)
 520                ],
 521                c.helper.constraint_coefficients(c.index),
 522                0.0,
 523            ),
 524            values=self._get_linear_constraints(constraints),
 525        )
 526
 527    def get_linear_constraint_lower_bounds(
 528        self, constraints: Optional[_IndexOrSeries] = None
 529    ) -> pd.Series:
 530        """Gets the lower bounds of all linear constraints in the set.
 531
 532        If `constraints` is a `pd.Index`, then the output will be indexed by the
 533        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 534        dimensions, then the output will be indexed by the same underlying
 535        dimensions.
 536
 537        Args:
 538          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 539            constraints from which to get the lower bounds. If unspecified, all
 540            linear constraints will be in scope.
 541
 542        Returns:
 543          pd.Series: The lower bounds of all linear constraints in the set.
 544        """
 545        return _attribute_series(
 546            func=lambda c: c.lower_bound,  # pylint: disable=protected-access
 547            values=self._get_linear_constraints(constraints),
 548        )
 549
 550    def get_linear_constraint_upper_bounds(
 551        self, constraints: Optional[_IndexOrSeries] = None
 552    ) -> pd.Series:
 553        """Gets the upper bounds of all linear constraints in the set.
 554
 555        If `constraints` is a `pd.Index`, then the output will be indexed by the
 556        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 557        dimensions, then the output will be indexed by the same underlying
 558        dimensions.
 559
 560        Args:
 561          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 562            constraints. If unspecified, all linear constraints will be in scope.
 563
 564        Returns:
 565          pd.Series: The upper bounds of all linear constraints in the set.
 566        """
 567        return _attribute_series(
 568            func=lambda c: c.upper_bound,  # pylint: disable=protected-access
 569            values=self._get_linear_constraints(constraints),
 570        )
 571
 572    def get_variables(self) -> pd.Index:
 573        """Gets all variables in the model."""
 574        return pd.Index(
 575            [self.var_from_index(i) for i in range(self.num_variables)],
 576            name="variable",
 577        )
 578
 579    def get_variable_lower_bounds(
 580        self, variables: Optional[_IndexOrSeries] = None
 581    ) -> pd.Series:
 582        """Gets the lower bounds of all variables in the set.
 583
 584        If `variables` is a `pd.Index`, then the output will be indexed by the
 585        variables. If `variables` is a `pd.Series` indexed by the underlying
 586        dimensions, then the output will be indexed by the same underlying
 587        dimensions.
 588
 589        Args:
 590          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
 591            from which to get the lower bounds. If unspecified, all variables will
 592            be in scope.
 593
 594        Returns:
 595          pd.Series: The lower bounds of all variables in the set.
 596        """
 597        return _attribute_series(
 598            func=lambda v: v.lower_bound,  # pylint: disable=protected-access
 599            values=self._get_variables(variables),
 600        )
 601
 602    def get_variable_upper_bounds(
 603        self, variables: Optional[_IndexOrSeries] = None
 604    ) -> pd.Series:
 605        """Gets the upper bounds of all variables in the set.
 606
 607        Args:
 608          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
 609            from which to get the upper bounds. If unspecified, all variables will
 610            be in scope.
 611
 612        Returns:
 613          pd.Series: The upper bounds of all variables in the set.
 614        """
 615        return _attribute_series(
 616            func=lambda v: v.upper_bound,  # pylint: disable=protected-access
 617            values=self._get_variables(variables),
 618        )
 619
 620    # Integer variable.
 621
 622    def new_var(
 623        self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str]
 624    ) -> Variable:
 625        """Create an integer variable with domain [lb, ub].
 626
 627        Args:
 628          lb: Lower bound of the variable.
 629          ub: Upper bound of the variable.
 630          is_integer: Indicates if the variable must take integral values.
 631          name: The name of the variable.
 632
 633        Returns:
 634          a variable whose domain is [lb, ub].
 635        """
 636        if name:
 637            return Variable(self.__helper, lb, ub, is_integer, name)
 638        else:
 639            return Variable(self.__helper, lb, ub, is_integer)
 640
 641    def new_int_var(
 642        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
 643    ) -> Variable:
 644        """Create an integer variable with domain [lb, ub].
 645
 646        Args:
 647          lb: Lower bound of the variable.
 648          ub: Upper bound of the variable.
 649          name: The name of the variable.
 650
 651        Returns:
 652          a variable whose domain is [lb, ub].
 653        """
 654
 655        return self.new_var(lb, ub, True, name)
 656
 657    def new_num_var(
 658        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
 659    ) -> Variable:
 660        """Create an integer variable with domain [lb, ub].
 661
 662        Args:
 663          lb: Lower bound of the variable.
 664          ub: Upper bound of the variable.
 665          name: The name of the variable.
 666
 667        Returns:
 668          a variable whose domain is [lb, ub].
 669        """
 670
 671        return self.new_var(lb, ub, False, name)
 672
 673    def new_bool_var(self, name: Optional[str] = None) -> Variable:
 674        """Creates a 0-1 variable with the given name."""
 675        return self.new_var(
 676            0, 1, True, name
 677        )  # pytype: disable=wrong-arg-types  # numpy-scalars
 678
 679    def new_constant(self, value: NumberT) -> Variable:
 680        """Declares a constant variable."""
 681        return self.new_var(value, value, False, None)
 682
 683    def new_var_series(
 684        self,
 685        name: str,
 686        index: pd.Index,
 687        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
 688        upper_bounds: Union[NumberT, pd.Series] = math.inf,
 689        is_integral: Union[bool, pd.Series] = False,
 690    ) -> pd.Series:
 691        """Creates a series of (scalar-valued) variables with the given name.
 692
 693        Args:
 694          name (str): Required. The name of the variable set.
 695          index (pd.Index): Required. The index to use for the variable set.
 696          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
 697            variables in the set. If a `pd.Series` is passed in, it will be based on
 698            the corresponding values of the pd.Series. Defaults to -inf.
 699          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
 700            variables in the set. If a `pd.Series` is passed in, it will be based on
 701            the corresponding values of the pd.Series. Defaults to +inf.
 702          is_integral (bool, pd.Series): Optional. Indicates if the variable can
 703            only take integer values. If a `pd.Series` is passed in, it will be
 704            based on the corresponding values of the pd.Series. Defaults to False.
 705
 706        Returns:
 707          pd.Series: The variable set indexed by its corresponding dimensions.
 708
 709        Raises:
 710          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 711          ValueError: if the `name` is not a valid identifier or already exists.
 712          ValueError: if the `lowerbound` is greater than the `upperbound`.
 713          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
 714          does not match the input index.
 715        """
 716        if not isinstance(index, pd.Index):
 717            raise TypeError("Non-index object is used as index")
 718        if not name.isidentifier():
 719            raise ValueError(f"name={name!r} is not a valid identifier")
 720        if (
 721            mbn.is_a_number(lower_bounds)
 722            and mbn.is_a_number(upper_bounds)
 723            and lower_bounds > upper_bounds
 724        ):
 725            raise ValueError(
 726                f"lower_bound={lower_bounds} is greater than"
 727                f" upper_bound={upper_bounds} for variable set={name!r}"
 728            )
 729        if (
 730            isinstance(is_integral, bool)
 731            and is_integral
 732            and mbn.is_a_number(lower_bounds)
 733            and mbn.is_a_number(upper_bounds)
 734            and math.isfinite(lower_bounds)
 735            and math.isfinite(upper_bounds)
 736            and math.ceil(lower_bounds) > math.floor(upper_bounds)
 737        ):
 738            raise ValueError(
 739                f"ceil(lower_bound={lower_bounds})={math.ceil(lower_bounds)}"
 740                f" is greater than floor({upper_bounds}) = {math.floor(upper_bounds)}"
 741                f" for variable set={name!r}"
 742            )
 743        lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index)
 744        upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index)
 745        is_integrals = _convert_to_series_and_validate_index(is_integral, index)
 746        return pd.Series(
 747            index=index,
 748            data=[
 749                # pylint: disable=g-complex-comprehension
 750                Variable(
 751                    self.__helper,
 752                    lower_bounds[i],
 753                    upper_bounds[i],
 754                    is_integrals[i],
 755                    f"{name}[{i}]",
 756                )
 757                for i in index
 758            ],
 759        )
 760
 761    def new_num_var_series(
 762        self,
 763        name: str,
 764        index: pd.Index,
 765        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
 766        upper_bounds: Union[NumberT, pd.Series] = math.inf,
 767    ) -> pd.Series:
 768        """Creates a series of continuous variables with the given name.
 769
 770        Args:
 771          name (str): Required. The name of the variable set.
 772          index (pd.Index): Required. The index to use for the variable set.
 773          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
 774            variables in the set. If a `pd.Series` is passed in, it will be based on
 775            the corresponding values of the pd.Series. Defaults to -inf.
 776          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
 777            variables in the set. If a `pd.Series` is passed in, it will be based on
 778            the corresponding values of the pd.Series. Defaults to +inf.
 779
 780        Returns:
 781          pd.Series: The variable set indexed by its corresponding dimensions.
 782
 783        Raises:
 784          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 785          ValueError: if the `name` is not a valid identifier or already exists.
 786          ValueError: if the `lowerbound` is greater than the `upperbound`.
 787          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
 788          does not match the input index.
 789        """
 790        return self.new_var_series(name, index, lower_bounds, upper_bounds, False)
 791
 792    def new_int_var_series(
 793        self,
 794        name: str,
 795        index: pd.Index,
 796        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
 797        upper_bounds: Union[NumberT, pd.Series] = math.inf,
 798    ) -> pd.Series:
 799        """Creates a series of integer variables with the given name.
 800
 801        Args:
 802          name (str): Required. The name of the variable set.
 803          index (pd.Index): Required. The index to use for the variable set.
 804          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
 805            variables in the set. If a `pd.Series` is passed in, it will be based on
 806            the corresponding values of the pd.Series. Defaults to -inf.
 807          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
 808            variables in the set. If a `pd.Series` is passed in, it will be based on
 809            the corresponding values of the pd.Series. Defaults to +inf.
 810
 811        Returns:
 812          pd.Series: The variable set indexed by its corresponding dimensions.
 813
 814        Raises:
 815          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 816          ValueError: if the `name` is not a valid identifier or already exists.
 817          ValueError: if the `lowerbound` is greater than the `upperbound`.
 818          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
 819          does not match the input index.
 820        """
 821        return self.new_var_series(name, index, lower_bounds, upper_bounds, True)
 822
 823    def new_bool_var_series(
 824        self,
 825        name: str,
 826        index: pd.Index,
 827    ) -> pd.Series:
 828        """Creates a series of Boolean variables with the given name.
 829
 830        Args:
 831          name (str): Required. The name of the variable set.
 832          index (pd.Index): Required. The index to use for the variable set.
 833
 834        Returns:
 835          pd.Series: The variable set indexed by its corresponding dimensions.
 836
 837        Raises:
 838          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 839          ValueError: if the `name` is not a valid identifier or already exists.
 840          ValueError: if the `lowerbound` is greater than the `upperbound`.
 841          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
 842          does not match the input index.
 843        """
 844        return self.new_var_series(name, index, 0, 1, True)
 845
 846    def var_from_index(self, index: IntegerT) -> Variable:
 847        """Rebuilds a variable object from the model and its index."""
 848        return Variable(self.__helper, index)
 849
 850    # Linear constraints.
 851
 852    def add_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
 853        self,
 854        linear_expr: LinearExprT,
 855        lb: NumberT = -math.inf,
 856        ub: NumberT = math.inf,
 857        name: Optional[str] = None,
 858    ) -> LinearConstraint:
 859        """Adds the constraint: `lb <= linear_expr <= ub` with the given name."""
 860        ct = LinearConstraint(self.__helper)
 861        if name:
 862            self.__helper.set_constraint_name(ct.index, name)
 863        if mbn.is_a_number(linear_expr):
 864            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
 865            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
 866        elif isinstance(linear_expr, LinearExpr):
 867            flat_expr = mbh.FlatExpr(linear_expr)
 868            # pylint: disable=protected-access
 869            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset)
 870            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset)
 871            self.__helper.add_terms_to_constraint(
 872                ct.index, flat_expr.vars, flat_expr.coeffs
 873            )
 874        else:
 875            raise TypeError(
 876                "Not supported:"
 877                f" Model.add_linear_constraint({type(linear_expr).__name__!r})"
 878            )
 879        return ct
 880
 881    def add(
 882        self, ct: Union[ConstraintT, pd.Series], name: Optional[str] = None
 883    ) -> Union[LinearConstraint, pd.Series]:
 884        """Adds a `BoundedLinearExpression` to the model.
 885
 886        Args:
 887          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
 888          name: An optional name.
 889
 890        Returns:
 891          An instance of the `Constraint` class.
 892
 893        Note that a special treatment is done when the argument does not contain any
 894        variable, and thus evaluates to True or False.
 895
 896        `model.add(True)` will create a constraint 0 <= empty sum <= 0.
 897        The constraint will be marked as under specified, and cannot be modified
 898        thereafter.
 899
 900        `model.add(False)` will create a constraint inf <= empty sum <= -inf. The
 901        constraint will be marked as under specified, and cannot be modified
 902        thereafter.
 903
 904        you can check the if a constraint is under specified by reading the
 905        `LinearConstraint.is_under_specified` property.
 906        """
 907        if isinstance(ct, mbh.BoundedLinearExpression):
 908            return _add_linear_constraint_to_helper(ct, self.__helper, name)
 909        elif isinstance(ct, bool):
 910            return _add_linear_constraint_to_helper(ct, self.__helper, name)
 911        elif isinstance(ct, pd.Series):
 912            return pd.Series(
 913                index=ct.index,
 914                data=[
 915                    _add_linear_constraint_to_helper(
 916                        expr, self.__helper, f"{name}[{i}]"
 917                    )
 918                    for (i, expr) in zip(ct.index, ct)
 919                ],
 920            )
 921        else:
 922            raise TypeError(f"Not supported: Model.add({type(ct).__name__!r})")
 923
 924    def linear_constraint_from_index(self, index: IntegerT) -> LinearConstraint:
 925        """Rebuilds a linear constraint object from the model and its index."""
 926        return LinearConstraint(self.__helper, index=index)
 927
 928    # Enforced Linear constraints.
 929
 930    def add_enforced_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
 931        self,
 932        linear_expr: LinearExprT,
 933        ivar: "Variable",
 934        ivalue: bool,
 935        lb: NumberT = -math.inf,
 936        ub: NumberT = math.inf,
 937        name: Optional[str] = None,
 938    ) -> EnforcedLinearConstraint:
 939        """Adds the constraint: `ivar == ivalue => lb <= linear_expr <= ub` with the given name."""
 940        ct = EnforcedLinearConstraint(self.__helper)
 941        ct.indicator_variable = ivar
 942        ct.indicator_value = ivalue
 943        if name:
 944            self.__helper.set_constraint_name(ct.index, name)
 945        if mbn.is_a_number(linear_expr):
 946            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
 947            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
 948        elif isinstance(linear_expr, LinearExpr):
 949            flat_expr = mbh.FlatExpr(linear_expr)
 950            # pylint: disable=protected-access
 951            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset)
 952            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset)
 953            self.__helper.add_terms_to_constraint(
 954                ct.index, flat_expr.vars, flat_expr.coeffs
 955            )
 956        else:
 957            raise TypeError(
 958                "Not supported:"
 959                f" Model.add_enforced_linear_constraint({type(linear_expr).__name__!r})"
 960            )
 961        return ct
 962
 963    def add_enforced(
 964        self,
 965        ct: Union[ConstraintT, pd.Series],
 966        var: Union[Variable, pd.Series],
 967        value: Union[bool, pd.Series],
 968        name: Optional[str] = None,
 969    ) -> Union[EnforcedLinearConstraint, pd.Series]:
 970        """Adds a `ivar == ivalue => BoundedLinearExpression` to the model.
 971
 972        Args:
 973          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
 974          var: The indicator variable
 975          value: the indicator value
 976          name: An optional name.
 977
 978        Returns:
 979          An instance of the `Constraint` class.
 980
 981        Note that a special treatment is done when the argument does not contain any
 982        variable, and thus evaluates to True or False.
 983
 984        model.add_enforced(True, ivar, ivalue) will create a constraint 0 <= empty
 985        sum <= 0
 986
 987        model.add_enforced(False, var, value) will create a constraint inf <=
 988        empty sum <= -inf
 989
 990        you can check the if a constraint is always false (lb=inf, ub=-inf) by
 991        calling EnforcedLinearConstraint.is_always_false()
 992        """
 993        if isinstance(ct, mbh.BoundedLinearExpression):
 994            return _add_enforced_linear_constraint_to_helper(
 995                ct, self.__helper, var, value, name
 996            )
 997        elif (
 998            isinstance(ct, bool)
 999            and isinstance(var, Variable)
1000            and isinstance(value, bool)
1001        ):
1002            return _add_enforced_linear_constraint_to_helper(
1003                ct, self.__helper, var, value, name
1004            )
1005        elif isinstance(ct, pd.Series):
1006            ivar_series = _convert_to_var_series_and_validate_index(var, ct.index)
1007            ivalue_series = _convert_to_series_and_validate_index(value, ct.index)
1008            return pd.Series(
1009                index=ct.index,
1010                data=[
1011                    _add_enforced_linear_constraint_to_helper(
1012                        expr,
1013                        self.__helper,
1014                        ivar_series[i],
1015                        ivalue_series[i],
1016                        f"{name}[{i}]",
1017                    )
1018                    for (i, expr) in zip(ct.index, ct)
1019                ],
1020            )
1021        else:
1022            raise TypeError(f"Not supported: Model.add_enforced({type(ct).__name__!r}")
1023
1024    def enforced_linear_constraint_from_index(
1025        self, index: IntegerT
1026    ) -> EnforcedLinearConstraint:
1027        """Rebuilds an enforced linear constraint object from the model and its index."""
1028        return EnforcedLinearConstraint(self.__helper, index=index)
1029
1030    # Objective.
1031    def minimize(self, linear_expr: LinearExprT) -> None:
1032        """Minimizes the given objective."""
1033        self.__optimize(linear_expr, False)
1034
1035    def maximize(self, linear_expr: LinearExprT) -> None:
1036        """Maximizes the given objective."""
1037        self.__optimize(linear_expr, True)
1038
1039    def __optimize(self, linear_expr: LinearExprT, maximize: bool) -> None:
1040        """Defines the objective."""
1041        self.helper.clear_objective()
1042        self.__helper.set_maximize(maximize)
1043        if mbn.is_a_number(linear_expr):
1044            self.helper.set_objective_offset(linear_expr)
1045        elif isinstance(linear_expr, Variable):
1046            self.helper.set_var_objective_coefficient(linear_expr.index, 1.0)
1047        elif isinstance(linear_expr, LinearExpr):
1048            flat_expr = mbh.FlatExpr(linear_expr)
1049            # pylint: disable=protected-access
1050            self.helper.set_objective_offset(flat_expr.offset)
1051            var_indices = [var.index for var in flat_expr.vars]
1052            self.helper.set_objective_coefficients(var_indices, flat_expr.coeffs)
1053        else:
1054            raise TypeError(
1055                "Not supported:"
1056                f" Model.minimize/maximize({type(linear_expr).__name__!r})"
1057            )
1058
1059    @property
1060    def objective_offset(self) -> np.double:
1061        """Returns the fixed offset of the objective."""
1062        return self.__helper.objective_offset()
1063
1064    @objective_offset.setter
1065    def objective_offset(self, value: NumberT) -> None:
1066        self.__helper.set_objective_offset(value)
1067
1068    def objective_expression(self) -> "LinearExpr":
1069        """Returns the expression to optimize."""
1070        variables: list[Variable] = []
1071        coefficients: list[numbers.Real] = []
1072        for variable in self.get_variables():
1073            coeff = self.__helper.var_objective_coefficient(variable.index)
1074            if coeff != 0.0:
1075                variables.append(variable)
1076                coefficients.append(coeff)
1077        return mbh.FlatExpr(variables, coefficients, self.__helper.objective_offset())
1078
1079    # Hints.
1080    def clear_hints(self):
1081        """Clears all solution hints."""
1082        self.__helper.clear_hints()
1083
1084    def add_hint(self, var: Variable, value: NumberT) -> None:
1085        """Adds var == value as a hint to the model.
1086
1087        Args:
1088          var: The variable of the hint
1089          value: The value of the hint
1090
1091        Note that variables must not appear more than once in the list of hints.
1092        """
1093        self.__helper.add_hint(var.index, value)
1094
1095    # Input/Output
1096    def export_to_lp_string(self, obfuscate: bool = False) -> str:
1097        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1098        options.obfuscate = obfuscate
1099        return self.__helper.export_to_lp_string(options)
1100
1101    def export_to_mps_string(self, obfuscate: bool = False) -> str:
1102        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1103        options.obfuscate = obfuscate
1104        return self.__helper.export_to_mps_string(options)
1105
1106    def write_to_mps_file(self, filename: str, obfuscate: bool = False) -> bool:
1107        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1108        options.obfuscate = obfuscate
1109        return self.__helper.write_to_mps_file(filename, options)
1110
1111    def export_to_proto(self) -> linear_solver_pb2.MPModelProto:
1112        """Exports the optimization model to a ProtoBuf format."""
1113        return mbh.to_mpmodel_proto(self.__helper)
1114
1115    def import_from_mps_string(self, mps_string: str) -> bool:
1116        """Reads a model from a MPS string."""
1117        return self.__helper.import_from_mps_string(mps_string)
1118
1119    def import_from_mps_file(self, mps_file: str) -> bool:
1120        """Reads a model from a .mps file."""
1121        return self.__helper.import_from_mps_file(mps_file)
1122
1123    def import_from_lp_string(self, lp_string: str) -> bool:
1124        """Reads a model from a LP string.
1125
1126        Note that this code is very limited, and will not support any real lp.
1127        It is only intented to be use to parse test lp problems.
1128
1129        Args:
1130          lp_string: The LP string to import.
1131
1132        Returns:
1133          True if the import was successful.
1134        """
1135        return self.__helper.import_from_lp_string(lp_string)
1136
1137    def import_from_lp_file(self, lp_file: str) -> bool:
1138        """Reads a model from a .lp file.
1139
1140        Note that this code is very limited, and will not support any real lp.
1141        It is only intented to be use to parse test lp problems.
1142
1143        Args:
1144          lp_file: The LP file to import.
1145
1146        Returns:
1147          True if the import was successful.
1148        """
1149        return self.__helper.import_from_lp_file(lp_file)
1150
1151    def import_from_proto_file(self, proto_file: str) -> bool:
1152        """Reads a model from a proto file."""
1153        return self.__helper.read_model_from_proto_file(proto_file)
1154
1155    def export_to_proto_file(self, proto_file: str) -> bool:
1156        """Writes a model to a proto file."""
1157        return self.__helper.write_model_to_proto_file(proto_file)
1158
1159    # Model getters and Setters
1160
1161    @property
1162    def num_variables(self) -> int:
1163        """Returns the number of variables in the model."""
1164        return self.__helper.num_variables()
1165
1166    @property
1167    def num_constraints(self) -> int:
1168        """The number of constraints in the model."""
1169        return self.__helper.num_constraints()
1170
1171    @property
1172    def name(self) -> str:
1173        """The name of the model."""
1174        return self.__helper.name()
1175
1176    @name.setter
1177    def name(self, name: str):
1178        self.__helper.set_name(name)
1179
1180    @property
1181    def helper(self) -> mbh.ModelBuilderHelper:
1182        """Returns the model builder helper."""
1183        return self.__helper
1184
1185
1186class Solver:
1187    """Main solver class.
1188
1189    The purpose of this class is to search for a solution to the model provided
1190    to the solve() method.
1191
1192    Once solve() is called, this class allows inspecting the solution found
1193    with the value() method, as well as general statistics about the solve
1194    procedure.
1195    """
1196
1197    def __init__(self, solver_name: str):
1198        self.__solve_helper: mbh.ModelSolverHelper = mbh.ModelSolverHelper(solver_name)
1199        self.log_callback: Optional[Callable[[str], None]] = None
1200
1201    def solver_is_supported(self) -> bool:
1202        """Checks whether the requested solver backend was found."""
1203        return self.__solve_helper.solver_is_supported()
1204
1205    # Solver backend and parameters.
1206    def set_time_limit_in_seconds(self, limit: NumberT) -> None:
1207        """Sets a time limit for the solve() call."""
1208        self.__solve_helper.set_time_limit_in_seconds(limit)
1209
1210    def set_solver_specific_parameters(self, parameters: str) -> None:
1211        """Sets parameters specific to the solver backend."""
1212        self.__solve_helper.set_solver_specific_parameters(parameters)
1213
1214    def enable_output(self, enabled: bool) -> None:
1215        """Controls the solver backend logs."""
1216        self.__solve_helper.enable_output(enabled)
1217
1218    def solve(self, model: Model) -> SolveStatus:
1219        """Solves a problem and passes each solution to the callback if not null."""
1220        if self.log_callback is not None:
1221            self.__solve_helper.set_log_callback(self.log_callback)
1222        else:
1223            self.__solve_helper.clear_log_callback()
1224        self.__solve_helper.solve(model.helper)
1225        return SolveStatus(self.__solve_helper.status())
1226
1227    def stop_search(self):
1228        """Stops the current search asynchronously."""
1229        self.__solve_helper.interrupt_solve()
1230
1231    def value(self, expr: LinearExprT) -> np.double:
1232        """Returns the value of a linear expression after solve."""
1233        if not self.__solve_helper.has_solution():
1234            return pd.NA
1235        if mbn.is_a_number(expr):
1236            return expr
1237        elif isinstance(expr, LinearExpr):
1238            return self.__solve_helper.expression_value(expr)
1239        else:
1240            raise TypeError(f"Unknown expression {type(expr).__name__!r}")
1241
1242    def values(self, variables: _IndexOrSeries) -> pd.Series:
1243        """Returns the values of the input variables.
1244
1245        If `variables` is a `pd.Index`, then the output will be indexed by the
1246        variables. If `variables` is a `pd.Series` indexed by the underlying
1247        dimensions, then the output will be indexed by the same underlying
1248        dimensions.
1249
1250        Args:
1251          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1252            get the values.
1253
1254        Returns:
1255          pd.Series: The values of all variables in the set.
1256        """
1257        if not self.__solve_helper.has_solution():
1258            return _attribute_series(func=lambda v: pd.NA, values=variables)
1259        return _attribute_series(
1260            func=lambda v: self.__solve_helper.variable_value(v.index),
1261            values=variables,
1262        )
1263
1264    def reduced_costs(self, variables: _IndexOrSeries) -> pd.Series:
1265        """Returns the reduced cost of the input variables.
1266
1267        If `variables` is a `pd.Index`, then the output will be indexed by the
1268        variables. If `variables` is a `pd.Series` indexed by the underlying
1269        dimensions, then the output will be indexed by the same underlying
1270        dimensions.
1271
1272        Args:
1273          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1274            get the values.
1275
1276        Returns:
1277          pd.Series: The reduced cost of all variables in the set.
1278        """
1279        if not self.__solve_helper.has_solution():
1280            return _attribute_series(func=lambda v: pd.NA, values=variables)
1281        return _attribute_series(
1282            func=lambda v: self.__solve_helper.reduced_cost(v.index),
1283            values=variables,
1284        )
1285
1286    def reduced_cost(self, var: Variable) -> np.double:
1287        """Returns the reduced cost of a linear expression after solve."""
1288        if not self.__solve_helper.has_solution():
1289            return pd.NA
1290        return self.__solve_helper.reduced_cost(var.index)
1291
1292    def dual_values(self, constraints: _IndexOrSeries) -> pd.Series:
1293        """Returns the dual values of the input constraints.
1294
1295        If `constraints` is a `pd.Index`, then the output will be indexed by the
1296        constraints. If `constraints` is a `pd.Series` indexed by the underlying
1297        dimensions, then the output will be indexed by the same underlying
1298        dimensions.
1299
1300        Args:
1301          constraints (Union[pd.Index, pd.Series]): The set of constraints from
1302            which to get the dual values.
1303
1304        Returns:
1305          pd.Series: The dual_values of all constraints in the set.
1306        """
1307        if not self.__solve_helper.has_solution():
1308            return _attribute_series(func=lambda v: pd.NA, values=constraints)
1309        return _attribute_series(
1310            func=lambda v: self.__solve_helper.dual_value(v.index),
1311            values=constraints,
1312        )
1313
1314    def dual_value(self, ct: LinearConstraint) -> np.double:
1315        """Returns the dual value of a linear constraint after solve."""
1316        if not self.__solve_helper.has_solution():
1317            return pd.NA
1318        return self.__solve_helper.dual_value(ct.index)
1319
1320    def activity(self, ct: LinearConstraint) -> np.double:
1321        """Returns the activity of a linear constraint after solve."""
1322        if not self.__solve_helper.has_solution():
1323            return pd.NA
1324        return self.__solve_helper.activity(ct.index)
1325
1326    @property
1327    def objective_value(self) -> np.double:
1328        """Returns the value of the objective after solve."""
1329        if not self.__solve_helper.has_solution():
1330            return pd.NA
1331        return self.__solve_helper.objective_value()
1332
1333    @property
1334    def best_objective_bound(self) -> np.double:
1335        """Returns the best lower (upper) bound found when min(max)imizing."""
1336        if not self.__solve_helper.has_solution():
1337            return pd.NA
1338        return self.__solve_helper.best_objective_bound()
1339
1340    @property
1341    def status_string(self) -> str:
1342        """Returns additional information of the last solve.
1343
1344        It can describe why the model is invalid.
1345        """
1346        return self.__solve_helper.status_string()
1347
1348    @property
1349    def wall_time(self) -> np.double:
1350        return self.__solve_helper.wall_time()
1351
1352    @property
1353    def user_time(self) -> np.double:
1354        return self.__solve_helper.user_time()
1355
1356
1357def _get_index(obj: _IndexOrSeries) -> pd.Index:
1358    """Returns the indices of `obj` as a `pd.Index`."""
1359    if isinstance(obj, pd.Series):
1360        return obj.index
1361    return obj
1362
1363
1364def _attribute_series(
1365    *,
1366    func: Callable[[_VariableOrConstraint], NumberT],
1367    values: _IndexOrSeries,
1368) -> pd.Series:
1369    """Returns the attributes of `values`.
1370
1371    Args:
1372      func: The function to call for getting the attribute data.
1373      values: The values that the function will be applied (element-wise) to.
1374
1375    Returns:
1376      pd.Series: The attribute values.
1377    """
1378    return pd.Series(
1379        data=[func(v) for v in values],
1380        index=_get_index(values),
1381    )
1382
1383
1384def _convert_to_series_and_validate_index(
1385    value_or_series: Union[bool, NumberT, pd.Series], index: pd.Index
1386) -> pd.Series:
1387    """Returns a pd.Series of the given index with the corresponding values.
1388
1389    Args:
1390      value_or_series: the values to be converted (if applicable).
1391      index: the index of the resulting pd.Series.
1392
1393    Returns:
1394      pd.Series: The set of values with the given index.
1395
1396    Raises:
1397      TypeError: If the type of `value_or_series` is not recognized.
1398      ValueError: If the index does not match.
1399    """
1400    if mbn.is_a_number(value_or_series) or isinstance(value_or_series, bool):
1401        result = pd.Series(data=value_or_series, index=index)
1402    elif isinstance(value_or_series, pd.Series):
1403        if value_or_series.index.equals(index):
1404            result = value_or_series
1405        else:
1406            raise ValueError("index does not match")
1407    else:
1408        raise TypeError("invalid type={type(value_or_series).__name!r}")
1409    return result
1410
1411
1412def _convert_to_var_series_and_validate_index(
1413    var_or_series: Union["Variable", pd.Series], index: pd.Index
1414) -> pd.Series:
1415    """Returns a pd.Series of the given index with the corresponding values.
1416
1417    Args:
1418      var_or_series: the variables to be converted (if applicable).
1419      index: the index of the resulting pd.Series.
1420
1421    Returns:
1422      pd.Series: The set of values with the given index.
1423
1424    Raises:
1425      TypeError: If the type of `value_or_series` is not recognized.
1426      ValueError: If the index does not match.
1427    """
1428    if isinstance(var_or_series, Variable):
1429        result = pd.Series(data=var_or_series, index=index)
1430    elif isinstance(var_or_series, pd.Series):
1431        if var_or_series.index.equals(index):
1432            result = var_or_series
1433        else:
1434            raise ValueError("index does not match")
1435    else:
1436        raise TypeError("invalid type={type(value_or_series).__name!r}")
1437    return result
1438
1439
1440# Compatibility.
1441ModelBuilder = Model
1442ModelSolver = Solver
NumberT = typing.Union[int, float, numbers.Real, numpy.number]
IntegerT = typing.Union[int, numbers.Integral, numpy.integer]
LinearExprT = typing.Union[LinearExpr, int, float, numbers.Real, numpy.number]
ConstraintT = typing.Union[BoundedLinearExpression, bool]

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)
class BoundedLinearExpression(pybind11_builtins.pybind11_object):

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)
vars

(arg0: ortools.linear_solver.python.model_builder_helper.FlatExpr) -> list[operations_research::mb::Variable]

def variable_indices(unknown):
class LinearExpr(pybind11_builtins.pybind11_object):

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)
LinearExpr(*args, **kwargs)
def sum(unknown):

sum(args, *kwargs) -> ortools.linear_solver.python.model_builder_helper.LinearExpr

Creates sum(expressions) [+ constant].

def weighted_sum(unknown):

weighted_sum(expressions: Sequence, coefficients: list[float], *, constant: float = 0.0) -> ortools.linear_solver.python.model_builder_helper.LinearExpr

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

def term(unknown):

term(args, *kwargs) Overloaded function.

  1. term(expr: ortools.linear_solver.python.model_builder_helper.LinearExpr, coeff: float) -> ortools.linear_solver.python.model_builder_helper.LinearExpr

Returns expr * coeff.

  1. term(expr: ortools.linear_solver.python.model_builder_helper.LinearExpr, coeff: float, *, constant: float) -> ortools.linear_solver.python.model_builder_helper.LinearExpr

Returns expr * coeff [+ constant].

  1. term(value: float, coeff: float, *, constant: float) -> ortools.linear_solver.python.model_builder_helper.LinearExpr

Returns value * coeff [+ constant].

def affine(unknown):

affine(args, *kwargs) Overloaded function.

  1. affine(expr: ortools.linear_solver.python.model_builder_helper.LinearExpr, coeff: float, constant: float = 0.0) -> ortools.linear_solver.python.model_builder_helper.LinearExpr

Returns expr * coeff + constant.

  1. affine(value: float, coeff: float, constant: float = 0.0) -> ortools.linear_solver.python.model_builder_helper.LinearExpr

Returns value * coeff + constant.

def constant(unknown):

constant(value: float) -> ortools.linear_solver.python.model_builder_helper.LinearExpr

Returns a constant linear expression.

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()
name

name(self: object) -> str

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>

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()

__init__(args, *kwargs) Overloaded function.

  1. __init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: int) -> None

  2. __init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: float, arg2: float, arg3: bool) -> None

  3. __init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: float, arg2: float, arg3: bool, arg4: str) -> None

  4. __init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: int, arg2: int, arg3: bool) -> None

  5. __init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: int, arg2: int, arg3: bool, arg4: str) -> None

helper

(arg0: ortools.linear_solver.python.model_builder_helper.Variable) -> operations_research::mb::ModelBuilderHelper

objective_coefficient
class LinearConstraint:
180class LinearConstraint:
181    """Stores a linear equation.
182
183    Example:
184        x = model.new_num_var(0, 10, 'x')
185        y = model.new_num_var(0, 10, 'y')
186
187        linear_constraint = model.add(x + 2 * y == 5)
188    """
189
190    def __init__(
191        self,
192        helper: mbh.ModelBuilderHelper,
193        *,
194        index: Optional[IntegerT] = None,
195        is_under_specified: bool = False,
196    ) -> None:
197        """LinearConstraint constructor.
198
199        Args:
200          helper: The pybind11 ModelBuilderHelper.
201          index: If specified, recreates a wrapper to an existing linear constraint.
202          is_under_specified: indicates if the constraint was created by
203            model.add(bool).
204        """
205        if index is None:
206            self.__index = helper.add_linear_constraint()
207        else:
208            self.__index = index
209        self.__helper: mbh.ModelBuilderHelper = helper
210        self.__is_under_specified = is_under_specified
211
212    def __hash__(self):
213        return hash((self.__helper, self.__index))
214
215    @property
216    def index(self) -> IntegerT:
217        """Returns the index of the constraint in the helper."""
218        return self.__index
219
220    @property
221    def helper(self) -> mbh.ModelBuilderHelper:
222        """Returns the ModelBuilderHelper instance."""
223        return self.__helper
224
225    @property
226    def lower_bound(self) -> np.double:
227        return self.__helper.constraint_lower_bound(self.__index)
228
229    @lower_bound.setter
230    def lower_bound(self, bound: NumberT) -> None:
231        self.assert_constraint_is_well_defined()
232        self.__helper.set_constraint_lower_bound(self.__index, bound)
233
234    @property
235    def upper_bound(self) -> np.double:
236        return self.__helper.constraint_upper_bound(self.__index)
237
238    @upper_bound.setter
239    def upper_bound(self, bound: NumberT) -> None:
240        self.assert_constraint_is_well_defined()
241        self.__helper.set_constraint_upper_bound(self.__index, bound)
242
243    @property
244    def name(self) -> str:
245        constraint_name = self.__helper.constraint_name(self.__index)
246        if constraint_name:
247            return constraint_name
248        return f"linear_constraint#{self.__index}"
249
250    @name.setter
251    def name(self, name: str) -> None:
252        return self.__helper.set_constraint_name(self.__index, name)
253
254    @property
255    def is_under_specified(self) -> bool:
256        """Returns True if the constraint is under specified.
257
258        Usually, it means that it was created by model.add(False) or model.add(True)
259        The effect is that modifying the constraint will raise an exception.
260        """
261        return self.__is_under_specified
262
263    def assert_constraint_is_well_defined(self) -> None:
264        """Raises an exception if the constraint is under specified."""
265        if self.__is_under_specified:
266            raise ValueError(
267                f"Constraint {self.index} is under specified and cannot be modified"
268            )
269
270    def __str__(self):
271        return self.name
272
273    def __repr__(self):
274        return (
275            f"LinearConstraint({self.name}, lb={self.lower_bound},"
276            f" ub={self.upper_bound},"
277            f" var_indices={self.helper.constraint_var_indices(self.index)},"
278            f" coefficients={self.helper.constraint_coefficients(self.index)})"
279        )
280
281    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
282        """Sets the coefficient of the variable in the constraint."""
283        self.assert_constraint_is_well_defined()
284        self.__helper.set_constraint_coefficient(self.__index, var.index, coeff)
285
286    def add_term(self, var: Variable, coeff: NumberT) -> None:
287        """Adds var * coeff to the constraint."""
288        self.assert_constraint_is_well_defined()
289        self.__helper.safe_add_term_to_constraint(self.__index, var.index, coeff)
290
291    def clear_terms(self) -> None:
292        """Clear all terms of the constraint."""
293        self.assert_constraint_is_well_defined()
294        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, is_under_specified: bool = False)
190    def __init__(
191        self,
192        helper: mbh.ModelBuilderHelper,
193        *,
194        index: Optional[IntegerT] = None,
195        is_under_specified: bool = False,
196    ) -> None:
197        """LinearConstraint constructor.
198
199        Args:
200          helper: The pybind11 ModelBuilderHelper.
201          index: If specified, recreates a wrapper to an existing linear constraint.
202          is_under_specified: indicates if the constraint was created by
203            model.add(bool).
204        """
205        if index is None:
206            self.__index = helper.add_linear_constraint()
207        else:
208            self.__index = index
209        self.__helper: mbh.ModelBuilderHelper = helper
210        self.__is_under_specified = is_under_specified

LinearConstraint constructor.

Arguments:
  • helper: The pybind11 ModelBuilderHelper.
  • index: If specified, recreates a wrapper to an existing linear constraint.
  • is_under_specified: indicates if the constraint was created by model.add(bool).
index: Union[int, numbers.Integral, numpy.integer]
215    @property
216    def index(self) -> IntegerT:
217        """Returns the index of the constraint in the helper."""
218        return self.__index

Returns the index of the constraint in the helper.

220    @property
221    def helper(self) -> mbh.ModelBuilderHelper:
222        """Returns the ModelBuilderHelper instance."""
223        return self.__helper

Returns the ModelBuilderHelper instance.

lower_bound: numpy.float64
225    @property
226    def lower_bound(self) -> np.double:
227        return self.__helper.constraint_lower_bound(self.__index)
upper_bound: numpy.float64
234    @property
235    def upper_bound(self) -> np.double:
236        return self.__helper.constraint_upper_bound(self.__index)
name: str
243    @property
244    def name(self) -> str:
245        constraint_name = self.__helper.constraint_name(self.__index)
246        if constraint_name:
247            return constraint_name
248        return f"linear_constraint#{self.__index}"
is_under_specified: bool
254    @property
255    def is_under_specified(self) -> bool:
256        """Returns True if the constraint is under specified.
257
258        Usually, it means that it was created by model.add(False) or model.add(True)
259        The effect is that modifying the constraint will raise an exception.
260        """
261        return self.__is_under_specified

Returns True if the constraint is under specified.

Usually, it means that it was created by model.add(False) or model.add(True) The effect is that modifying the constraint will raise an exception.

def assert_constraint_is_well_defined(self) -> None:
263    def assert_constraint_is_well_defined(self) -> None:
264        """Raises an exception if the constraint is under specified."""
265        if self.__is_under_specified:
266            raise ValueError(
267                f"Constraint {self.index} is under specified and cannot be modified"
268            )

Raises an exception if the constraint is under specified.

def set_coefficient( self, var: Variable, coeff: Union[int, float, numbers.Real, numpy.number]) -> None:
281    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
282        """Sets the coefficient of the variable in the constraint."""
283        self.assert_constraint_is_well_defined()
284        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:
286    def add_term(self, var: Variable, coeff: NumberT) -> None:
287        """Adds var * coeff to the constraint."""
288        self.assert_constraint_is_well_defined()
289        self.__helper.safe_add_term_to_constraint(self.__index, var.index, coeff)

Adds var * coeff to the constraint.

def clear_terms(self) -> None:
291    def clear_terms(self) -> None:
292        """Clear all terms of the constraint."""
293        self.assert_constraint_is_well_defined()
294        self.__helper.clear_constraint_terms(self.__index)

Clear all terms of the constraint.

class EnforcedLinearConstraint:
297class EnforcedLinearConstraint:
298    """Stores an enforced linear equation, also name indicator constraint.
299
300    Example:
301        x = model.new_num_var(0, 10, 'x')
302        y = model.new_num_var(0, 10, 'y')
303        z = model.new_bool_var('z')
304
305        enforced_linear_constraint = model.add_enforced(x + 2 * y == 5, z, False)
306    """
307
308    def __init__(
309        self,
310        helper: mbh.ModelBuilderHelper,
311        *,
312        index: Optional[IntegerT] = None,
313        is_under_specified: bool = False,
314    ) -> None:
315        """EnforcedLinearConstraint constructor.
316
317        Args:
318          helper: The pybind11 ModelBuilderHelper.
319          index: If specified, recreates a wrapper to an existing linear constraint.
320          is_under_specified: indicates if the constraint was created by
321            model.add(bool).
322        """
323        if index is None:
324            self.__index = helper.add_enforced_linear_constraint()
325        else:
326            if not helper.is_enforced_linear_constraint(index):
327                raise ValueError(
328                    f"the given index {index} does not refer to an enforced linear"
329                    " constraint"
330                )
331
332            self.__index = index
333        self.__helper: mbh.ModelBuilderHelper = helper
334        self.__is_under_specified = is_under_specified
335
336    @property
337    def index(self) -> IntegerT:
338        """Returns the index of the constraint in the helper."""
339        return self.__index
340
341    @property
342    def helper(self) -> mbh.ModelBuilderHelper:
343        """Returns the ModelBuilderHelper instance."""
344        return self.__helper
345
346    @property
347    def lower_bound(self) -> np.double:
348        return self.__helper.enforced_constraint_lower_bound(self.__index)
349
350    @lower_bound.setter
351    def lower_bound(self, bound: NumberT) -> None:
352        self.assert_constraint_is_well_defined()
353        self.__helper.set_enforced_constraint_lower_bound(self.__index, bound)
354
355    @property
356    def upper_bound(self) -> np.double:
357        return self.__helper.enforced_constraint_upper_bound(self.__index)
358
359    @upper_bound.setter
360    def upper_bound(self, bound: NumberT) -> None:
361        self.assert_constraint_is_well_defined()
362        self.__helper.set_enforced_constraint_upper_bound(self.__index, bound)
363
364    @property
365    def indicator_variable(self) -> "Variable":
366        enforcement_var_index = (
367            self.__helper.enforced_constraint_indicator_variable_index(self.__index)
368        )
369        return Variable(self.__helper, enforcement_var_index)
370
371    @indicator_variable.setter
372    def indicator_variable(self, var: "Variable") -> None:
373        self.__helper.set_enforced_constraint_indicator_variable_index(
374            self.__index, var.index
375        )
376
377    @property
378    def indicator_value(self) -> bool:
379        return self.__helper.enforced_constraint_indicator_value(self.__index)
380
381    @indicator_value.setter
382    def indicator_value(self, value: bool) -> None:
383        self.__helper.set_enforced_constraint_indicator_value(self.__index, value)
384
385    @property
386    def name(self) -> str:
387        constraint_name = self.__helper.enforced_constraint_name(self.__index)
388        if constraint_name:
389            return constraint_name
390        return f"enforced_linear_constraint#{self.__index}"
391
392    @name.setter
393    def name(self, name: str) -> None:
394        return self.__helper.set_enforced_constraint_name(self.__index, name)
395
396    @property
397    def is_under_specified(self) -> bool:
398        """Returns True if the constraint is under specified.
399
400        Usually, it means that it was created by model.add(False) or model.add(True)
401        The effect is that modifying the constraint will raise an exception.
402        """
403        return self.__is_under_specified
404
405    def assert_constraint_is_well_defined(self) -> None:
406        """Raises an exception if the constraint is under specified."""
407        if self.__is_under_specified:
408            raise ValueError(
409                f"Constraint {self.index} is under specified and cannot be modified"
410            )
411
412    def __str__(self):
413        return self.name
414
415    def __repr__(self):
416        return (
417            f"EnforcedLinearConstraint({self.name}, lb={self.lower_bound},"
418            f" ub={self.upper_bound},"
419            f" var_indices={self.helper.enforced_constraint_var_indices(self.index)},"
420            f" coefficients={self.helper.enforced_constraint_coefficients(self.index)},"
421            f" indicator_variable={self.indicator_variable}"
422            f" indicator_value={self.indicator_value})"
423        )
424
425    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
426        """Sets the coefficient of the variable in the constraint."""
427        self.assert_constraint_is_well_defined()
428        self.__helper.set_enforced_constraint_coefficient(
429            self.__index, var.index, coeff
430        )
431
432    def add_term(self, var: Variable, coeff: NumberT) -> None:
433        """Adds var * coeff to the constraint."""
434        self.assert_constraint_is_well_defined()
435        self.__helper.safe_add_term_to_enforced_constraint(
436            self.__index, var.index, coeff
437        )
438
439    def clear_terms(self) -> None:
440        """Clear all terms of the constraint."""
441        self.assert_constraint_is_well_defined()
442        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, is_under_specified: bool = False)
308    def __init__(
309        self,
310        helper: mbh.ModelBuilderHelper,
311        *,
312        index: Optional[IntegerT] = None,
313        is_under_specified: bool = False,
314    ) -> None:
315        """EnforcedLinearConstraint constructor.
316
317        Args:
318          helper: The pybind11 ModelBuilderHelper.
319          index: If specified, recreates a wrapper to an existing linear constraint.
320          is_under_specified: indicates if the constraint was created by
321            model.add(bool).
322        """
323        if index is None:
324            self.__index = helper.add_enforced_linear_constraint()
325        else:
326            if not helper.is_enforced_linear_constraint(index):
327                raise ValueError(
328                    f"the given index {index} does not refer to an enforced linear"
329                    " constraint"
330                )
331
332            self.__index = index
333        self.__helper: mbh.ModelBuilderHelper = helper
334        self.__is_under_specified = is_under_specified

EnforcedLinearConstraint constructor.

Arguments:
  • helper: The pybind11 ModelBuilderHelper.
  • index: If specified, recreates a wrapper to an existing linear constraint.
  • is_under_specified: indicates if the constraint was created by model.add(bool).
index: Union[int, numbers.Integral, numpy.integer]
336    @property
337    def index(self) -> IntegerT:
338        """Returns the index of the constraint in the helper."""
339        return self.__index

Returns the index of the constraint in the helper.

341    @property
342    def helper(self) -> mbh.ModelBuilderHelper:
343        """Returns the ModelBuilderHelper instance."""
344        return self.__helper

Returns the ModelBuilderHelper instance.

lower_bound: numpy.float64
346    @property
347    def lower_bound(self) -> np.double:
348        return self.__helper.enforced_constraint_lower_bound(self.__index)
upper_bound: numpy.float64
355    @property
356    def upper_bound(self) -> np.double:
357        return self.__helper.enforced_constraint_upper_bound(self.__index)
indicator_variable: Variable
364    @property
365    def indicator_variable(self) -> "Variable":
366        enforcement_var_index = (
367            self.__helper.enforced_constraint_indicator_variable_index(self.__index)
368        )
369        return Variable(self.__helper, enforcement_var_index)
indicator_value: bool
377    @property
378    def indicator_value(self) -> bool:
379        return self.__helper.enforced_constraint_indicator_value(self.__index)
name: str
385    @property
386    def name(self) -> str:
387        constraint_name = self.__helper.enforced_constraint_name(self.__index)
388        if constraint_name:
389            return constraint_name
390        return f"enforced_linear_constraint#{self.__index}"
is_under_specified: bool
396    @property
397    def is_under_specified(self) -> bool:
398        """Returns True if the constraint is under specified.
399
400        Usually, it means that it was created by model.add(False) or model.add(True)
401        The effect is that modifying the constraint will raise an exception.
402        """
403        return self.__is_under_specified

Returns True if the constraint is under specified.

Usually, it means that it was created by model.add(False) or model.add(True) The effect is that modifying the constraint will raise an exception.

def assert_constraint_is_well_defined(self) -> None:
405    def assert_constraint_is_well_defined(self) -> None:
406        """Raises an exception if the constraint is under specified."""
407        if self.__is_under_specified:
408            raise ValueError(
409                f"Constraint {self.index} is under specified and cannot be modified"
410            )

Raises an exception if the constraint is under specified.

def set_coefficient( self, var: Variable, coeff: Union[int, float, numbers.Real, numpy.number]) -> None:
425    def set_coefficient(self, var: Variable, coeff: NumberT) -> None:
426        """Sets the coefficient of the variable in the constraint."""
427        self.assert_constraint_is_well_defined()
428        self.__helper.set_enforced_constraint_coefficient(
429            self.__index, var.index, coeff
430        )

Sets the coefficient of the variable in the constraint.

def add_term( self, var: Variable, coeff: Union[int, float, numbers.Real, numpy.number]) -> None:
432    def add_term(self, var: Variable, coeff: NumberT) -> None:
433        """Adds var * coeff to the constraint."""
434        self.assert_constraint_is_well_defined()
435        self.__helper.safe_add_term_to_enforced_constraint(
436            self.__index, var.index, coeff
437        )

Adds var * coeff to the constraint.

def clear_terms(self) -> None:
439    def clear_terms(self) -> None:
440        """Clear all terms of the constraint."""
441        self.assert_constraint_is_well_defined()
442        self.__helper.clear_enforced_constraint_terms(self.__index)

Clear all terms of the constraint.

class Model:
 445class Model:
 446    """Methods for building a linear model.
 447
 448    Methods beginning with:
 449
 450    * ```new_``` create integer, boolean, or interval variables.
 451    * ```add_``` create new constraints and add them to the model.
 452    """
 453
 454    def __init__(self):
 455        self.__helper: mbh.ModelBuilderHelper = mbh.ModelBuilderHelper()
 456
 457    def clone(self) -> "Model":
 458        """Returns a clone of the current model."""
 459        clone = Model()
 460        clone.helper.overwrite_model(self.helper)
 461        return clone
 462
 463    @typing.overload
 464    def _get_linear_constraints(self, constraints: Optional[pd.Index]) -> pd.Index: ...
 465
 466    @typing.overload
 467    def _get_linear_constraints(self, constraints: pd.Series) -> pd.Series: ...
 468
 469    def _get_linear_constraints(
 470        self, constraints: Optional[_IndexOrSeries] = None
 471    ) -> _IndexOrSeries:
 472        if constraints is None:
 473            return self.get_linear_constraints()
 474        return constraints
 475
 476    @typing.overload
 477    def _get_variables(self, variables: Optional[pd.Index]) -> pd.Index: ...
 478
 479    @typing.overload
 480    def _get_variables(self, variables: pd.Series) -> pd.Series: ...
 481
 482    def _get_variables(
 483        self, variables: Optional[_IndexOrSeries] = None
 484    ) -> _IndexOrSeries:
 485        if variables is None:
 486            return self.get_variables()
 487        return variables
 488
 489    def get_linear_constraints(self) -> pd.Index:
 490        """Gets all linear constraints in the model."""
 491        return pd.Index(
 492            [self.linear_constraint_from_index(i) for i in range(self.num_constraints)],
 493            name="linear_constraint",
 494        )
 495
 496    def get_linear_constraint_expressions(
 497        self, constraints: Optional[_IndexOrSeries] = None
 498    ) -> pd.Series:
 499        """Gets the expressions of all linear constraints in the set.
 500
 501        If `constraints` is a `pd.Index`, then the output will be indexed by the
 502        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 503        dimensions, then the output will be indexed by the same underlying
 504        dimensions.
 505
 506        Args:
 507          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 508            constraints from which to get the expressions. If unspecified, all
 509            linear constraints will be in scope.
 510
 511        Returns:
 512          pd.Series: The expressions of all linear constraints in the set.
 513        """
 514        return _attribute_series(
 515            # pylint: disable=g-long-lambda
 516            func=lambda c: mbh.FlatExpr(
 517                # pylint: disable=g-complex-comprehension
 518                [
 519                    Variable(self.__helper, var_id)
 520                    for var_id in c.helper.constraint_var_indices(c.index)
 521                ],
 522                c.helper.constraint_coefficients(c.index),
 523                0.0,
 524            ),
 525            values=self._get_linear_constraints(constraints),
 526        )
 527
 528    def get_linear_constraint_lower_bounds(
 529        self, constraints: Optional[_IndexOrSeries] = None
 530    ) -> pd.Series:
 531        """Gets the lower bounds of all linear constraints in the set.
 532
 533        If `constraints` is a `pd.Index`, then the output will be indexed by the
 534        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 535        dimensions, then the output will be indexed by the same underlying
 536        dimensions.
 537
 538        Args:
 539          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 540            constraints from which to get the lower bounds. If unspecified, all
 541            linear constraints will be in scope.
 542
 543        Returns:
 544          pd.Series: The lower bounds of all linear constraints in the set.
 545        """
 546        return _attribute_series(
 547            func=lambda c: c.lower_bound,  # pylint: disable=protected-access
 548            values=self._get_linear_constraints(constraints),
 549        )
 550
 551    def get_linear_constraint_upper_bounds(
 552        self, constraints: Optional[_IndexOrSeries] = None
 553    ) -> pd.Series:
 554        """Gets the upper bounds of all linear constraints in the set.
 555
 556        If `constraints` is a `pd.Index`, then the output will be indexed by the
 557        constraints. If `constraints` is a `pd.Series` indexed by the underlying
 558        dimensions, then the output will be indexed by the same underlying
 559        dimensions.
 560
 561        Args:
 562          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
 563            constraints. If unspecified, all linear constraints will be in scope.
 564
 565        Returns:
 566          pd.Series: The upper bounds of all linear constraints in the set.
 567        """
 568        return _attribute_series(
 569            func=lambda c: c.upper_bound,  # pylint: disable=protected-access
 570            values=self._get_linear_constraints(constraints),
 571        )
 572
 573    def get_variables(self) -> pd.Index:
 574        """Gets all variables in the model."""
 575        return pd.Index(
 576            [self.var_from_index(i) for i in range(self.num_variables)],
 577            name="variable",
 578        )
 579
 580    def get_variable_lower_bounds(
 581        self, variables: Optional[_IndexOrSeries] = None
 582    ) -> pd.Series:
 583        """Gets the lower bounds of all variables in the set.
 584
 585        If `variables` is a `pd.Index`, then the output will be indexed by the
 586        variables. If `variables` is a `pd.Series` indexed by the underlying
 587        dimensions, then the output will be indexed by the same underlying
 588        dimensions.
 589
 590        Args:
 591          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
 592            from which to get the lower bounds. If unspecified, all variables will
 593            be in scope.
 594
 595        Returns:
 596          pd.Series: The lower bounds of all variables in the set.
 597        """
 598        return _attribute_series(
 599            func=lambda v: v.lower_bound,  # pylint: disable=protected-access
 600            values=self._get_variables(variables),
 601        )
 602
 603    def get_variable_upper_bounds(
 604        self, variables: Optional[_IndexOrSeries] = None
 605    ) -> pd.Series:
 606        """Gets the upper bounds of all variables in the set.
 607
 608        Args:
 609          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
 610            from which to get the upper bounds. If unspecified, all variables will
 611            be in scope.
 612
 613        Returns:
 614          pd.Series: The upper bounds of all variables in the set.
 615        """
 616        return _attribute_series(
 617            func=lambda v: v.upper_bound,  # pylint: disable=protected-access
 618            values=self._get_variables(variables),
 619        )
 620
 621    # Integer variable.
 622
 623    def new_var(
 624        self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str]
 625    ) -> Variable:
 626        """Create an integer variable with domain [lb, ub].
 627
 628        Args:
 629          lb: Lower bound of the variable.
 630          ub: Upper bound of the variable.
 631          is_integer: Indicates if the variable must take integral values.
 632          name: The name of the variable.
 633
 634        Returns:
 635          a variable whose domain is [lb, ub].
 636        """
 637        if name:
 638            return Variable(self.__helper, lb, ub, is_integer, name)
 639        else:
 640            return Variable(self.__helper, lb, ub, is_integer)
 641
 642    def new_int_var(
 643        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
 644    ) -> Variable:
 645        """Create an integer variable with domain [lb, ub].
 646
 647        Args:
 648          lb: Lower bound of the variable.
 649          ub: Upper bound of the variable.
 650          name: The name of the variable.
 651
 652        Returns:
 653          a variable whose domain is [lb, ub].
 654        """
 655
 656        return self.new_var(lb, ub, True, name)
 657
 658    def new_num_var(
 659        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
 660    ) -> Variable:
 661        """Create an integer variable with domain [lb, ub].
 662
 663        Args:
 664          lb: Lower bound of the variable.
 665          ub: Upper bound of the variable.
 666          name: The name of the variable.
 667
 668        Returns:
 669          a variable whose domain is [lb, ub].
 670        """
 671
 672        return self.new_var(lb, ub, False, name)
 673
 674    def new_bool_var(self, name: Optional[str] = None) -> Variable:
 675        """Creates a 0-1 variable with the given name."""
 676        return self.new_var(
 677            0, 1, True, name
 678        )  # pytype: disable=wrong-arg-types  # numpy-scalars
 679
 680    def new_constant(self, value: NumberT) -> Variable:
 681        """Declares a constant variable."""
 682        return self.new_var(value, value, False, None)
 683
 684    def new_var_series(
 685        self,
 686        name: str,
 687        index: pd.Index,
 688        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
 689        upper_bounds: Union[NumberT, pd.Series] = math.inf,
 690        is_integral: Union[bool, pd.Series] = False,
 691    ) -> pd.Series:
 692        """Creates a series of (scalar-valued) variables with the given name.
 693
 694        Args:
 695          name (str): Required. The name of the variable set.
 696          index (pd.Index): Required. The index to use for the variable set.
 697          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
 698            variables in the set. If a `pd.Series` is passed in, it will be based on
 699            the corresponding values of the pd.Series. Defaults to -inf.
 700          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
 701            variables in the set. If a `pd.Series` is passed in, it will be based on
 702            the corresponding values of the pd.Series. Defaults to +inf.
 703          is_integral (bool, pd.Series): Optional. Indicates if the variable can
 704            only take integer values. If a `pd.Series` is passed in, it will be
 705            based on the corresponding values of the pd.Series. Defaults to False.
 706
 707        Returns:
 708          pd.Series: The variable set indexed by its corresponding dimensions.
 709
 710        Raises:
 711          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 712          ValueError: if the `name` is not a valid identifier or already exists.
 713          ValueError: if the `lowerbound` is greater than the `upperbound`.
 714          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
 715          does not match the input index.
 716        """
 717        if not isinstance(index, pd.Index):
 718            raise TypeError("Non-index object is used as index")
 719        if not name.isidentifier():
 720            raise ValueError(f"name={name!r} is not a valid identifier")
 721        if (
 722            mbn.is_a_number(lower_bounds)
 723            and mbn.is_a_number(upper_bounds)
 724            and lower_bounds > upper_bounds
 725        ):
 726            raise ValueError(
 727                f"lower_bound={lower_bounds} is greater than"
 728                f" upper_bound={upper_bounds} for variable set={name!r}"
 729            )
 730        if (
 731            isinstance(is_integral, bool)
 732            and is_integral
 733            and mbn.is_a_number(lower_bounds)
 734            and mbn.is_a_number(upper_bounds)
 735            and math.isfinite(lower_bounds)
 736            and math.isfinite(upper_bounds)
 737            and math.ceil(lower_bounds) > math.floor(upper_bounds)
 738        ):
 739            raise ValueError(
 740                f"ceil(lower_bound={lower_bounds})={math.ceil(lower_bounds)}"
 741                f" is greater than floor({upper_bounds}) = {math.floor(upper_bounds)}"
 742                f" for variable set={name!r}"
 743            )
 744        lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index)
 745        upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index)
 746        is_integrals = _convert_to_series_and_validate_index(is_integral, index)
 747        return pd.Series(
 748            index=index,
 749            data=[
 750                # pylint: disable=g-complex-comprehension
 751                Variable(
 752                    self.__helper,
 753                    lower_bounds[i],
 754                    upper_bounds[i],
 755                    is_integrals[i],
 756                    f"{name}[{i}]",
 757                )
 758                for i in index
 759            ],
 760        )
 761
 762    def new_num_var_series(
 763        self,
 764        name: str,
 765        index: pd.Index,
 766        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
 767        upper_bounds: Union[NumberT, pd.Series] = math.inf,
 768    ) -> pd.Series:
 769        """Creates a series of continuous variables with the given name.
 770
 771        Args:
 772          name (str): Required. The name of the variable set.
 773          index (pd.Index): Required. The index to use for the variable set.
 774          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
 775            variables in the set. If a `pd.Series` is passed in, it will be based on
 776            the corresponding values of the pd.Series. Defaults to -inf.
 777          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
 778            variables in the set. If a `pd.Series` is passed in, it will be based on
 779            the corresponding values of the pd.Series. Defaults to +inf.
 780
 781        Returns:
 782          pd.Series: The variable set indexed by its corresponding dimensions.
 783
 784        Raises:
 785          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 786          ValueError: if the `name` is not a valid identifier or already exists.
 787          ValueError: if the `lowerbound` is greater than the `upperbound`.
 788          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
 789          does not match the input index.
 790        """
 791        return self.new_var_series(name, index, lower_bounds, upper_bounds, False)
 792
 793    def new_int_var_series(
 794        self,
 795        name: str,
 796        index: pd.Index,
 797        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
 798        upper_bounds: Union[NumberT, pd.Series] = math.inf,
 799    ) -> pd.Series:
 800        """Creates a series of integer variables with the given name.
 801
 802        Args:
 803          name (str): Required. The name of the variable set.
 804          index (pd.Index): Required. The index to use for the variable set.
 805          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
 806            variables in the set. If a `pd.Series` is passed in, it will be based on
 807            the corresponding values of the pd.Series. Defaults to -inf.
 808          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
 809            variables in the set. If a `pd.Series` is passed in, it will be based on
 810            the corresponding values of the pd.Series. Defaults to +inf.
 811
 812        Returns:
 813          pd.Series: The variable set indexed by its corresponding dimensions.
 814
 815        Raises:
 816          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 817          ValueError: if the `name` is not a valid identifier or already exists.
 818          ValueError: if the `lowerbound` is greater than the `upperbound`.
 819          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
 820          does not match the input index.
 821        """
 822        return self.new_var_series(name, index, lower_bounds, upper_bounds, True)
 823
 824    def new_bool_var_series(
 825        self,
 826        name: str,
 827        index: pd.Index,
 828    ) -> pd.Series:
 829        """Creates a series of Boolean variables with the given name.
 830
 831        Args:
 832          name (str): Required. The name of the variable set.
 833          index (pd.Index): Required. The index to use for the variable set.
 834
 835        Returns:
 836          pd.Series: The variable set indexed by its corresponding dimensions.
 837
 838        Raises:
 839          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
 840          ValueError: if the `name` is not a valid identifier or already exists.
 841          ValueError: if the `lowerbound` is greater than the `upperbound`.
 842          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
 843          does not match the input index.
 844        """
 845        return self.new_var_series(name, index, 0, 1, True)
 846
 847    def var_from_index(self, index: IntegerT) -> Variable:
 848        """Rebuilds a variable object from the model and its index."""
 849        return Variable(self.__helper, index)
 850
 851    # Linear constraints.
 852
 853    def add_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
 854        self,
 855        linear_expr: LinearExprT,
 856        lb: NumberT = -math.inf,
 857        ub: NumberT = math.inf,
 858        name: Optional[str] = None,
 859    ) -> LinearConstraint:
 860        """Adds the constraint: `lb <= linear_expr <= ub` with the given name."""
 861        ct = LinearConstraint(self.__helper)
 862        if name:
 863            self.__helper.set_constraint_name(ct.index, name)
 864        if mbn.is_a_number(linear_expr):
 865            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
 866            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
 867        elif isinstance(linear_expr, LinearExpr):
 868            flat_expr = mbh.FlatExpr(linear_expr)
 869            # pylint: disable=protected-access
 870            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset)
 871            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset)
 872            self.__helper.add_terms_to_constraint(
 873                ct.index, flat_expr.vars, flat_expr.coeffs
 874            )
 875        else:
 876            raise TypeError(
 877                "Not supported:"
 878                f" Model.add_linear_constraint({type(linear_expr).__name__!r})"
 879            )
 880        return ct
 881
 882    def add(
 883        self, ct: Union[ConstraintT, pd.Series], name: Optional[str] = None
 884    ) -> Union[LinearConstraint, pd.Series]:
 885        """Adds a `BoundedLinearExpression` to the model.
 886
 887        Args:
 888          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
 889          name: An optional name.
 890
 891        Returns:
 892          An instance of the `Constraint` class.
 893
 894        Note that a special treatment is done when the argument does not contain any
 895        variable, and thus evaluates to True or False.
 896
 897        `model.add(True)` will create a constraint 0 <= empty sum <= 0.
 898        The constraint will be marked as under specified, and cannot be modified
 899        thereafter.
 900
 901        `model.add(False)` will create a constraint inf <= empty sum <= -inf. The
 902        constraint will be marked as under specified, and cannot be modified
 903        thereafter.
 904
 905        you can check the if a constraint is under specified by reading the
 906        `LinearConstraint.is_under_specified` property.
 907        """
 908        if isinstance(ct, mbh.BoundedLinearExpression):
 909            return _add_linear_constraint_to_helper(ct, self.__helper, name)
 910        elif isinstance(ct, bool):
 911            return _add_linear_constraint_to_helper(ct, self.__helper, name)
 912        elif isinstance(ct, pd.Series):
 913            return pd.Series(
 914                index=ct.index,
 915                data=[
 916                    _add_linear_constraint_to_helper(
 917                        expr, self.__helper, f"{name}[{i}]"
 918                    )
 919                    for (i, expr) in zip(ct.index, ct)
 920                ],
 921            )
 922        else:
 923            raise TypeError(f"Not supported: Model.add({type(ct).__name__!r})")
 924
 925    def linear_constraint_from_index(self, index: IntegerT) -> LinearConstraint:
 926        """Rebuilds a linear constraint object from the model and its index."""
 927        return LinearConstraint(self.__helper, index=index)
 928
 929    # Enforced Linear constraints.
 930
 931    def add_enforced_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
 932        self,
 933        linear_expr: LinearExprT,
 934        ivar: "Variable",
 935        ivalue: bool,
 936        lb: NumberT = -math.inf,
 937        ub: NumberT = math.inf,
 938        name: Optional[str] = None,
 939    ) -> EnforcedLinearConstraint:
 940        """Adds the constraint: `ivar == ivalue => lb <= linear_expr <= ub` with the given name."""
 941        ct = EnforcedLinearConstraint(self.__helper)
 942        ct.indicator_variable = ivar
 943        ct.indicator_value = ivalue
 944        if name:
 945            self.__helper.set_constraint_name(ct.index, name)
 946        if mbn.is_a_number(linear_expr):
 947            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
 948            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
 949        elif isinstance(linear_expr, LinearExpr):
 950            flat_expr = mbh.FlatExpr(linear_expr)
 951            # pylint: disable=protected-access
 952            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset)
 953            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset)
 954            self.__helper.add_terms_to_constraint(
 955                ct.index, flat_expr.vars, flat_expr.coeffs
 956            )
 957        else:
 958            raise TypeError(
 959                "Not supported:"
 960                f" Model.add_enforced_linear_constraint({type(linear_expr).__name__!r})"
 961            )
 962        return ct
 963
 964    def add_enforced(
 965        self,
 966        ct: Union[ConstraintT, pd.Series],
 967        var: Union[Variable, pd.Series],
 968        value: Union[bool, pd.Series],
 969        name: Optional[str] = None,
 970    ) -> Union[EnforcedLinearConstraint, pd.Series]:
 971        """Adds a `ivar == ivalue => BoundedLinearExpression` to the model.
 972
 973        Args:
 974          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
 975          var: The indicator variable
 976          value: the indicator value
 977          name: An optional name.
 978
 979        Returns:
 980          An instance of the `Constraint` class.
 981
 982        Note that a special treatment is done when the argument does not contain any
 983        variable, and thus evaluates to True or False.
 984
 985        model.add_enforced(True, ivar, ivalue) will create a constraint 0 <= empty
 986        sum <= 0
 987
 988        model.add_enforced(False, var, value) will create a constraint inf <=
 989        empty sum <= -inf
 990
 991        you can check the if a constraint is always false (lb=inf, ub=-inf) by
 992        calling EnforcedLinearConstraint.is_always_false()
 993        """
 994        if isinstance(ct, mbh.BoundedLinearExpression):
 995            return _add_enforced_linear_constraint_to_helper(
 996                ct, self.__helper, var, value, name
 997            )
 998        elif (
 999            isinstance(ct, bool)
1000            and isinstance(var, Variable)
1001            and isinstance(value, bool)
1002        ):
1003            return _add_enforced_linear_constraint_to_helper(
1004                ct, self.__helper, var, value, name
1005            )
1006        elif isinstance(ct, pd.Series):
1007            ivar_series = _convert_to_var_series_and_validate_index(var, ct.index)
1008            ivalue_series = _convert_to_series_and_validate_index(value, ct.index)
1009            return pd.Series(
1010                index=ct.index,
1011                data=[
1012                    _add_enforced_linear_constraint_to_helper(
1013                        expr,
1014                        self.__helper,
1015                        ivar_series[i],
1016                        ivalue_series[i],
1017                        f"{name}[{i}]",
1018                    )
1019                    for (i, expr) in zip(ct.index, ct)
1020                ],
1021            )
1022        else:
1023            raise TypeError(f"Not supported: Model.add_enforced({type(ct).__name__!r}")
1024
1025    def enforced_linear_constraint_from_index(
1026        self, index: IntegerT
1027    ) -> EnforcedLinearConstraint:
1028        """Rebuilds an enforced linear constraint object from the model and its index."""
1029        return EnforcedLinearConstraint(self.__helper, index=index)
1030
1031    # Objective.
1032    def minimize(self, linear_expr: LinearExprT) -> None:
1033        """Minimizes the given objective."""
1034        self.__optimize(linear_expr, False)
1035
1036    def maximize(self, linear_expr: LinearExprT) -> None:
1037        """Maximizes the given objective."""
1038        self.__optimize(linear_expr, True)
1039
1040    def __optimize(self, linear_expr: LinearExprT, maximize: bool) -> None:
1041        """Defines the objective."""
1042        self.helper.clear_objective()
1043        self.__helper.set_maximize(maximize)
1044        if mbn.is_a_number(linear_expr):
1045            self.helper.set_objective_offset(linear_expr)
1046        elif isinstance(linear_expr, Variable):
1047            self.helper.set_var_objective_coefficient(linear_expr.index, 1.0)
1048        elif isinstance(linear_expr, LinearExpr):
1049            flat_expr = mbh.FlatExpr(linear_expr)
1050            # pylint: disable=protected-access
1051            self.helper.set_objective_offset(flat_expr.offset)
1052            var_indices = [var.index for var in flat_expr.vars]
1053            self.helper.set_objective_coefficients(var_indices, flat_expr.coeffs)
1054        else:
1055            raise TypeError(
1056                "Not supported:"
1057                f" Model.minimize/maximize({type(linear_expr).__name__!r})"
1058            )
1059
1060    @property
1061    def objective_offset(self) -> np.double:
1062        """Returns the fixed offset of the objective."""
1063        return self.__helper.objective_offset()
1064
1065    @objective_offset.setter
1066    def objective_offset(self, value: NumberT) -> None:
1067        self.__helper.set_objective_offset(value)
1068
1069    def objective_expression(self) -> "LinearExpr":
1070        """Returns the expression to optimize."""
1071        variables: list[Variable] = []
1072        coefficients: list[numbers.Real] = []
1073        for variable in self.get_variables():
1074            coeff = self.__helper.var_objective_coefficient(variable.index)
1075            if coeff != 0.0:
1076                variables.append(variable)
1077                coefficients.append(coeff)
1078        return mbh.FlatExpr(variables, coefficients, self.__helper.objective_offset())
1079
1080    # Hints.
1081    def clear_hints(self):
1082        """Clears all solution hints."""
1083        self.__helper.clear_hints()
1084
1085    def add_hint(self, var: Variable, value: NumberT) -> None:
1086        """Adds var == value as a hint to the model.
1087
1088        Args:
1089          var: The variable of the hint
1090          value: The value of the hint
1091
1092        Note that variables must not appear more than once in the list of hints.
1093        """
1094        self.__helper.add_hint(var.index, value)
1095
1096    # Input/Output
1097    def export_to_lp_string(self, obfuscate: bool = False) -> str:
1098        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1099        options.obfuscate = obfuscate
1100        return self.__helper.export_to_lp_string(options)
1101
1102    def export_to_mps_string(self, obfuscate: bool = False) -> str:
1103        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1104        options.obfuscate = obfuscate
1105        return self.__helper.export_to_mps_string(options)
1106
1107    def write_to_mps_file(self, filename: str, obfuscate: bool = False) -> bool:
1108        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1109        options.obfuscate = obfuscate
1110        return self.__helper.write_to_mps_file(filename, options)
1111
1112    def export_to_proto(self) -> linear_solver_pb2.MPModelProto:
1113        """Exports the optimization model to a ProtoBuf format."""
1114        return mbh.to_mpmodel_proto(self.__helper)
1115
1116    def import_from_mps_string(self, mps_string: str) -> bool:
1117        """Reads a model from a MPS string."""
1118        return self.__helper.import_from_mps_string(mps_string)
1119
1120    def import_from_mps_file(self, mps_file: str) -> bool:
1121        """Reads a model from a .mps file."""
1122        return self.__helper.import_from_mps_file(mps_file)
1123
1124    def import_from_lp_string(self, lp_string: str) -> bool:
1125        """Reads a model from a LP string.
1126
1127        Note that this code is very limited, and will not support any real lp.
1128        It is only intented to be use to parse test lp problems.
1129
1130        Args:
1131          lp_string: The LP string to import.
1132
1133        Returns:
1134          True if the import was successful.
1135        """
1136        return self.__helper.import_from_lp_string(lp_string)
1137
1138    def import_from_lp_file(self, lp_file: str) -> bool:
1139        """Reads a model from a .lp file.
1140
1141        Note that this code is very limited, and will not support any real lp.
1142        It is only intented to be use to parse test lp problems.
1143
1144        Args:
1145          lp_file: The LP file to import.
1146
1147        Returns:
1148          True if the import was successful.
1149        """
1150        return self.__helper.import_from_lp_file(lp_file)
1151
1152    def import_from_proto_file(self, proto_file: str) -> bool:
1153        """Reads a model from a proto file."""
1154        return self.__helper.read_model_from_proto_file(proto_file)
1155
1156    def export_to_proto_file(self, proto_file: str) -> bool:
1157        """Writes a model to a proto file."""
1158        return self.__helper.write_model_to_proto_file(proto_file)
1159
1160    # Model getters and Setters
1161
1162    @property
1163    def num_variables(self) -> int:
1164        """Returns the number of variables in the model."""
1165        return self.__helper.num_variables()
1166
1167    @property
1168    def num_constraints(self) -> int:
1169        """The number of constraints in the model."""
1170        return self.__helper.num_constraints()
1171
1172    @property
1173    def name(self) -> str:
1174        """The name of the model."""
1175        return self.__helper.name()
1176
1177    @name.setter
1178    def name(self, name: str):
1179        self.__helper.set_name(name)
1180
1181    @property
1182    def helper(self) -> mbh.ModelBuilderHelper:
1183        """Returns the model builder helper."""
1184        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:
457    def clone(self) -> "Model":
458        """Returns a clone of the current model."""
459        clone = Model()
460        clone.helper.overwrite_model(self.helper)
461        return clone

Returns a clone of the current model.

def get_linear_constraints(self) -> pandas.core.indexes.base.Index:
489    def get_linear_constraints(self) -> pd.Index:
490        """Gets all linear constraints in the model."""
491        return pd.Index(
492            [self.linear_constraint_from_index(i) for i in range(self.num_constraints)],
493            name="linear_constraint",
494        )

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:
496    def get_linear_constraint_expressions(
497        self, constraints: Optional[_IndexOrSeries] = None
498    ) -> pd.Series:
499        """Gets the expressions of all linear constraints in the set.
500
501        If `constraints` is a `pd.Index`, then the output will be indexed by the
502        constraints. If `constraints` is a `pd.Series` indexed by the underlying
503        dimensions, then the output will be indexed by the same underlying
504        dimensions.
505
506        Args:
507          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
508            constraints from which to get the expressions. If unspecified, all
509            linear constraints will be in scope.
510
511        Returns:
512          pd.Series: The expressions of all linear constraints in the set.
513        """
514        return _attribute_series(
515            # pylint: disable=g-long-lambda
516            func=lambda c: mbh.FlatExpr(
517                # pylint: disable=g-complex-comprehension
518                [
519                    Variable(self.__helper, var_id)
520                    for var_id in c.helper.constraint_var_indices(c.index)
521                ],
522                c.helper.constraint_coefficients(c.index),
523                0.0,
524            ),
525            values=self._get_linear_constraints(constraints),
526        )

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:
528    def get_linear_constraint_lower_bounds(
529        self, constraints: Optional[_IndexOrSeries] = None
530    ) -> pd.Series:
531        """Gets the lower bounds of all linear constraints in the set.
532
533        If `constraints` is a `pd.Index`, then the output will be indexed by the
534        constraints. If `constraints` is a `pd.Series` indexed by the underlying
535        dimensions, then the output will be indexed by the same underlying
536        dimensions.
537
538        Args:
539          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
540            constraints from which to get the lower bounds. If unspecified, all
541            linear constraints will be in scope.
542
543        Returns:
544          pd.Series: The lower bounds of all linear constraints in the set.
545        """
546        return _attribute_series(
547            func=lambda c: c.lower_bound,  # pylint: disable=protected-access
548            values=self._get_linear_constraints(constraints),
549        )

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:
551    def get_linear_constraint_upper_bounds(
552        self, constraints: Optional[_IndexOrSeries] = None
553    ) -> pd.Series:
554        """Gets the upper bounds of all linear constraints in the set.
555
556        If `constraints` is a `pd.Index`, then the output will be indexed by the
557        constraints. If `constraints` is a `pd.Series` indexed by the underlying
558        dimensions, then the output will be indexed by the same underlying
559        dimensions.
560
561        Args:
562          constraints (Union[pd.Index, pd.Series]): Optional. The set of linear
563            constraints. If unspecified, all linear constraints will be in scope.
564
565        Returns:
566          pd.Series: The upper bounds of all linear constraints in the set.
567        """
568        return _attribute_series(
569            func=lambda c: c.upper_bound,  # pylint: disable=protected-access
570            values=self._get_linear_constraints(constraints),
571        )

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:
573    def get_variables(self) -> pd.Index:
574        """Gets all variables in the model."""
575        return pd.Index(
576            [self.var_from_index(i) for i in range(self.num_variables)],
577            name="variable",
578        )

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:
580    def get_variable_lower_bounds(
581        self, variables: Optional[_IndexOrSeries] = None
582    ) -> pd.Series:
583        """Gets the lower bounds of all variables in the set.
584
585        If `variables` is a `pd.Index`, then the output will be indexed by the
586        variables. If `variables` is a `pd.Series` indexed by the underlying
587        dimensions, then the output will be indexed by the same underlying
588        dimensions.
589
590        Args:
591          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
592            from which to get the lower bounds. If unspecified, all variables will
593            be in scope.
594
595        Returns:
596          pd.Series: The lower bounds of all variables in the set.
597        """
598        return _attribute_series(
599            func=lambda v: v.lower_bound,  # pylint: disable=protected-access
600            values=self._get_variables(variables),
601        )

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:
603    def get_variable_upper_bounds(
604        self, variables: Optional[_IndexOrSeries] = None
605    ) -> pd.Series:
606        """Gets the upper bounds of all variables in the set.
607
608        Args:
609          variables (Union[pd.Index, pd.Series]): Optional. The set of variables
610            from which to get the upper bounds. If unspecified, all variables will
611            be in scope.
612
613        Returns:
614          pd.Series: The upper bounds of all variables in the set.
615        """
616        return _attribute_series(
617            func=lambda v: v.upper_bound,  # pylint: disable=protected-access
618            values=self._get_variables(variables),
619        )

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:
623    def new_var(
624        self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str]
625    ) -> Variable:
626        """Create an integer variable with domain [lb, ub].
627
628        Args:
629          lb: Lower bound of the variable.
630          ub: Upper bound of the variable.
631          is_integer: Indicates if the variable must take integral values.
632          name: The name of the variable.
633
634        Returns:
635          a variable whose domain is [lb, ub].
636        """
637        if name:
638            return Variable(self.__helper, lb, ub, is_integer, name)
639        else:
640            return Variable(self.__helper, lb, ub, is_integer)

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:
642    def new_int_var(
643        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
644    ) -> Variable:
645        """Create an integer variable with domain [lb, ub].
646
647        Args:
648          lb: Lower bound of the variable.
649          ub: Upper bound of the variable.
650          name: The name of the variable.
651
652        Returns:
653          a variable whose domain is [lb, ub].
654        """
655
656        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:
658    def new_num_var(
659        self, lb: NumberT, ub: NumberT, name: Optional[str] = None
660    ) -> Variable:
661        """Create an integer variable with domain [lb, ub].
662
663        Args:
664          lb: Lower bound of the variable.
665          ub: Upper bound of the variable.
666          name: The name of the variable.
667
668        Returns:
669          a variable whose domain is [lb, ub].
670        """
671
672        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:
674    def new_bool_var(self, name: Optional[str] = None) -> Variable:
675        """Creates a 0-1 variable with the given name."""
676        return self.new_var(
677            0, 1, True, name
678        )  # 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:
680    def new_constant(self, value: NumberT) -> Variable:
681        """Declares a constant variable."""
682        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:
684    def new_var_series(
685        self,
686        name: str,
687        index: pd.Index,
688        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
689        upper_bounds: Union[NumberT, pd.Series] = math.inf,
690        is_integral: Union[bool, pd.Series] = False,
691    ) -> pd.Series:
692        """Creates a series of (scalar-valued) variables with the given name.
693
694        Args:
695          name (str): Required. The name of the variable set.
696          index (pd.Index): Required. The index to use for the variable set.
697          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
698            variables in the set. If a `pd.Series` is passed in, it will be based on
699            the corresponding values of the pd.Series. Defaults to -inf.
700          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
701            variables in the set. If a `pd.Series` is passed in, it will be based on
702            the corresponding values of the pd.Series. Defaults to +inf.
703          is_integral (bool, pd.Series): Optional. Indicates if the variable can
704            only take integer values. If a `pd.Series` is passed in, it will be
705            based on the corresponding values of the pd.Series. Defaults to False.
706
707        Returns:
708          pd.Series: The variable set indexed by its corresponding dimensions.
709
710        Raises:
711          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
712          ValueError: if the `name` is not a valid identifier or already exists.
713          ValueError: if the `lowerbound` is greater than the `upperbound`.
714          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
715          does not match the input index.
716        """
717        if not isinstance(index, pd.Index):
718            raise TypeError("Non-index object is used as index")
719        if not name.isidentifier():
720            raise ValueError(f"name={name!r} is not a valid identifier")
721        if (
722            mbn.is_a_number(lower_bounds)
723            and mbn.is_a_number(upper_bounds)
724            and lower_bounds > upper_bounds
725        ):
726            raise ValueError(
727                f"lower_bound={lower_bounds} is greater than"
728                f" upper_bound={upper_bounds} for variable set={name!r}"
729            )
730        if (
731            isinstance(is_integral, bool)
732            and is_integral
733            and mbn.is_a_number(lower_bounds)
734            and mbn.is_a_number(upper_bounds)
735            and math.isfinite(lower_bounds)
736            and math.isfinite(upper_bounds)
737            and math.ceil(lower_bounds) > math.floor(upper_bounds)
738        ):
739            raise ValueError(
740                f"ceil(lower_bound={lower_bounds})={math.ceil(lower_bounds)}"
741                f" is greater than floor({upper_bounds}) = {math.floor(upper_bounds)}"
742                f" for variable set={name!r}"
743            )
744        lower_bounds = _convert_to_series_and_validate_index(lower_bounds, index)
745        upper_bounds = _convert_to_series_and_validate_index(upper_bounds, index)
746        is_integrals = _convert_to_series_and_validate_index(is_integral, index)
747        return pd.Series(
748            index=index,
749            data=[
750                # pylint: disable=g-complex-comprehension
751                Variable(
752                    self.__helper,
753                    lower_bounds[i],
754                    upper_bounds[i],
755                    is_integrals[i],
756                    f"{name}[{i}]",
757                )
758                for i in index
759            ],
760        )

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:
762    def new_num_var_series(
763        self,
764        name: str,
765        index: pd.Index,
766        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
767        upper_bounds: Union[NumberT, pd.Series] = math.inf,
768    ) -> pd.Series:
769        """Creates a series of continuous variables with the given name.
770
771        Args:
772          name (str): Required. The name of the variable set.
773          index (pd.Index): Required. The index to use for the variable set.
774          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
775            variables in the set. If a `pd.Series` is passed in, it will be based on
776            the corresponding values of the pd.Series. Defaults to -inf.
777          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
778            variables in the set. If a `pd.Series` is passed in, it will be based on
779            the corresponding values of the pd.Series. Defaults to +inf.
780
781        Returns:
782          pd.Series: The variable set indexed by its corresponding dimensions.
783
784        Raises:
785          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
786          ValueError: if the `name` is not a valid identifier or already exists.
787          ValueError: if the `lowerbound` is greater than the `upperbound`.
788          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
789          does not match the input index.
790        """
791        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:
793    def new_int_var_series(
794        self,
795        name: str,
796        index: pd.Index,
797        lower_bounds: Union[NumberT, pd.Series] = -math.inf,
798        upper_bounds: Union[NumberT, pd.Series] = math.inf,
799    ) -> pd.Series:
800        """Creates a series of integer variables with the given name.
801
802        Args:
803          name (str): Required. The name of the variable set.
804          index (pd.Index): Required. The index to use for the variable set.
805          lower_bounds (Union[int, float, pd.Series]): Optional. A lower bound for
806            variables in the set. If a `pd.Series` is passed in, it will be based on
807            the corresponding values of the pd.Series. Defaults to -inf.
808          upper_bounds (Union[int, float, pd.Series]): Optional. An upper bound for
809            variables in the set. If a `pd.Series` is passed in, it will be based on
810            the corresponding values of the pd.Series. Defaults to +inf.
811
812        Returns:
813          pd.Series: The variable set indexed by its corresponding dimensions.
814
815        Raises:
816          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
817          ValueError: if the `name` is not a valid identifier or already exists.
818          ValueError: if the `lowerbound` is greater than the `upperbound`.
819          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
820          does not match the input index.
821        """
822        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:
824    def new_bool_var_series(
825        self,
826        name: str,
827        index: pd.Index,
828    ) -> pd.Series:
829        """Creates a series of Boolean variables with the given name.
830
831        Args:
832          name (str): Required. The name of the variable set.
833          index (pd.Index): Required. The index to use for the variable set.
834
835        Returns:
836          pd.Series: The variable set indexed by its corresponding dimensions.
837
838        Raises:
839          TypeError: if the `index` is invalid (e.g. a `DataFrame`).
840          ValueError: if the `name` is not a valid identifier or already exists.
841          ValueError: if the `lowerbound` is greater than the `upperbound`.
842          ValueError: if the index of `lower_bound`, `upper_bound`, or `is_integer`
843          does not match the input index.
844        """
845        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:
847    def var_from_index(self, index: IntegerT) -> Variable:
848        """Rebuilds a variable object from the model and its index."""
849        return Variable(self.__helper, index)

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:
853    def add_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
854        self,
855        linear_expr: LinearExprT,
856        lb: NumberT = -math.inf,
857        ub: NumberT = math.inf,
858        name: Optional[str] = None,
859    ) -> LinearConstraint:
860        """Adds the constraint: `lb <= linear_expr <= ub` with the given name."""
861        ct = LinearConstraint(self.__helper)
862        if name:
863            self.__helper.set_constraint_name(ct.index, name)
864        if mbn.is_a_number(linear_expr):
865            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
866            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
867        elif isinstance(linear_expr, LinearExpr):
868            flat_expr = mbh.FlatExpr(linear_expr)
869            # pylint: disable=protected-access
870            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset)
871            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset)
872            self.__helper.add_terms_to_constraint(
873                ct.index, flat_expr.vars, flat_expr.coeffs
874            )
875        else:
876            raise TypeError(
877                "Not supported:"
878                f" Model.add_linear_constraint({type(linear_expr).__name__!r})"
879            )
880        return ct

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

def add( self, ct: Union[BoundedLinearExpression, bool, pandas.core.series.Series], name: Optional[str] = None) -> Union[LinearConstraint, pandas.core.series.Series]:
882    def add(
883        self, ct: Union[ConstraintT, pd.Series], name: Optional[str] = None
884    ) -> Union[LinearConstraint, pd.Series]:
885        """Adds a `BoundedLinearExpression` to the model.
886
887        Args:
888          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
889          name: An optional name.
890
891        Returns:
892          An instance of the `Constraint` class.
893
894        Note that a special treatment is done when the argument does not contain any
895        variable, and thus evaluates to True or False.
896
897        `model.add(True)` will create a constraint 0 <= empty sum <= 0.
898        The constraint will be marked as under specified, and cannot be modified
899        thereafter.
900
901        `model.add(False)` will create a constraint inf <= empty sum <= -inf. The
902        constraint will be marked as under specified, and cannot be modified
903        thereafter.
904
905        you can check the if a constraint is under specified by reading the
906        `LinearConstraint.is_under_specified` property.
907        """
908        if isinstance(ct, mbh.BoundedLinearExpression):
909            return _add_linear_constraint_to_helper(ct, self.__helper, name)
910        elif isinstance(ct, bool):
911            return _add_linear_constraint_to_helper(ct, self.__helper, name)
912        elif isinstance(ct, pd.Series):
913            return pd.Series(
914                index=ct.index,
915                data=[
916                    _add_linear_constraint_to_helper(
917                        expr, self.__helper, f"{name}[{i}]"
918                    )
919                    for (i, expr) in zip(ct.index, ct)
920                ],
921            )
922        else:
923            raise TypeError(f"Not supported: Model.add({type(ct).__name__!r})")

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. The constraint will be marked as under specified, and cannot be modified thereafter.

model.add(False) will create a constraint inf <= empty sum <= -inf. The constraint will be marked as under specified, and cannot be modified thereafter.

you can check the if a constraint is under specified by reading the LinearConstraint.is_under_specified property.

def linear_constraint_from_index( self, index: Union[int, numbers.Integral, numpy.integer]) -> LinearConstraint:
925    def linear_constraint_from_index(self, index: IntegerT) -> LinearConstraint:
926        """Rebuilds a linear constraint object from the model and its index."""
927        return LinearConstraint(self.__helper, index=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:
931    def add_enforced_linear_constraint(  # pytype: disable=annotation-type-mismatch  # numpy-scalars
932        self,
933        linear_expr: LinearExprT,
934        ivar: "Variable",
935        ivalue: bool,
936        lb: NumberT = -math.inf,
937        ub: NumberT = math.inf,
938        name: Optional[str] = None,
939    ) -> EnforcedLinearConstraint:
940        """Adds the constraint: `ivar == ivalue => lb <= linear_expr <= ub` with the given name."""
941        ct = EnforcedLinearConstraint(self.__helper)
942        ct.indicator_variable = ivar
943        ct.indicator_value = ivalue
944        if name:
945            self.__helper.set_constraint_name(ct.index, name)
946        if mbn.is_a_number(linear_expr):
947            self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr)
948            self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr)
949        elif isinstance(linear_expr, LinearExpr):
950            flat_expr = mbh.FlatExpr(linear_expr)
951            # pylint: disable=protected-access
952            self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset)
953            self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset)
954            self.__helper.add_terms_to_constraint(
955                ct.index, flat_expr.vars, flat_expr.coeffs
956            )
957        else:
958            raise TypeError(
959                "Not supported:"
960                f" Model.add_enforced_linear_constraint({type(linear_expr).__name__!r})"
961            )
962        return ct

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

def add_enforced( self, ct: Union[BoundedLinearExpression, 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]:
 964    def add_enforced(
 965        self,
 966        ct: Union[ConstraintT, pd.Series],
 967        var: Union[Variable, pd.Series],
 968        value: Union[bool, pd.Series],
 969        name: Optional[str] = None,
 970    ) -> Union[EnforcedLinearConstraint, pd.Series]:
 971        """Adds a `ivar == ivalue => BoundedLinearExpression` to the model.
 972
 973        Args:
 974          ct: A [`BoundedLinearExpression`](#boundedlinearexpression).
 975          var: The indicator variable
 976          value: the indicator value
 977          name: An optional name.
 978
 979        Returns:
 980          An instance of the `Constraint` class.
 981
 982        Note that a special treatment is done when the argument does not contain any
 983        variable, and thus evaluates to True or False.
 984
 985        model.add_enforced(True, ivar, ivalue) will create a constraint 0 <= empty
 986        sum <= 0
 987
 988        model.add_enforced(False, var, value) will create a constraint inf <=
 989        empty sum <= -inf
 990
 991        you can check the if a constraint is always false (lb=inf, ub=-inf) by
 992        calling EnforcedLinearConstraint.is_always_false()
 993        """
 994        if isinstance(ct, mbh.BoundedLinearExpression):
 995            return _add_enforced_linear_constraint_to_helper(
 996                ct, self.__helper, var, value, name
 997            )
 998        elif (
 999            isinstance(ct, bool)
1000            and isinstance(var, Variable)
1001            and isinstance(value, bool)
1002        ):
1003            return _add_enforced_linear_constraint_to_helper(
1004                ct, self.__helper, var, value, name
1005            )
1006        elif isinstance(ct, pd.Series):
1007            ivar_series = _convert_to_var_series_and_validate_index(var, ct.index)
1008            ivalue_series = _convert_to_series_and_validate_index(value, ct.index)
1009            return pd.Series(
1010                index=ct.index,
1011                data=[
1012                    _add_enforced_linear_constraint_to_helper(
1013                        expr,
1014                        self.__helper,
1015                        ivar_series[i],
1016                        ivalue_series[i],
1017                        f"{name}[{i}]",
1018                    )
1019                    for (i, expr) in zip(ct.index, ct)
1020                ],
1021            )
1022        else:
1023            raise TypeError(f"Not supported: Model.add_enforced({type(ct).__name__!r}")

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:
1025    def enforced_linear_constraint_from_index(
1026        self, index: IntegerT
1027    ) -> EnforcedLinearConstraint:
1028        """Rebuilds an enforced linear constraint object from the model and its index."""
1029        return EnforcedLinearConstraint(self.__helper, index=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:
1032    def minimize(self, linear_expr: LinearExprT) -> None:
1033        """Minimizes the given objective."""
1034        self.__optimize(linear_expr, False)

Minimizes the given objective.

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

Maximizes the given objective.

objective_offset: numpy.float64
1060    @property
1061    def objective_offset(self) -> np.double:
1062        """Returns the fixed offset of the objective."""
1063        return self.__helper.objective_offset()

Returns the fixed offset of the objective.

def objective_expression(self) -> LinearExpr:
1069    def objective_expression(self) -> "LinearExpr":
1070        """Returns the expression to optimize."""
1071        variables: list[Variable] = []
1072        coefficients: list[numbers.Real] = []
1073        for variable in self.get_variables():
1074            coeff = self.__helper.var_objective_coefficient(variable.index)
1075            if coeff != 0.0:
1076                variables.append(variable)
1077                coefficients.append(coeff)
1078        return mbh.FlatExpr(variables, coefficients, self.__helper.objective_offset())

Returns the expression to optimize.

def clear_hints(self):
1081    def clear_hints(self):
1082        """Clears all solution hints."""
1083        self.__helper.clear_hints()

Clears all solution hints.

def add_hint( self, var: Variable, value: Union[int, float, numbers.Real, numpy.number]) -> None:
1085    def add_hint(self, var: Variable, value: NumberT) -> None:
1086        """Adds var == value as a hint to the model.
1087
1088        Args:
1089          var: The variable of the hint
1090          value: The value of the hint
1091
1092        Note that variables must not appear more than once in the list of hints.
1093        """
1094        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:
1097    def export_to_lp_string(self, obfuscate: bool = False) -> str:
1098        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1099        options.obfuscate = obfuscate
1100        return self.__helper.export_to_lp_string(options)
def export_to_mps_string(self, obfuscate: bool = False) -> str:
1102    def export_to_mps_string(self, obfuscate: bool = False) -> str:
1103        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1104        options.obfuscate = obfuscate
1105        return self.__helper.export_to_mps_string(options)
def write_to_mps_file(self, filename: str, obfuscate: bool = False) -> bool:
1107    def write_to_mps_file(self, filename: str, obfuscate: bool = False) -> bool:
1108        options: mbh.MPModelExportOptions = mbh.MPModelExportOptions()
1109        options.obfuscate = obfuscate
1110        return self.__helper.write_to_mps_file(filename, options)
def export_to_proto(self) -> ortools.linear_solver.linear_solver_pb2.MPModelProto:
1112    def export_to_proto(self) -> linear_solver_pb2.MPModelProto:
1113        """Exports the optimization model to a ProtoBuf format."""
1114        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:
1116    def import_from_mps_string(self, mps_string: str) -> bool:
1117        """Reads a model from a MPS string."""
1118        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:
1120    def import_from_mps_file(self, mps_file: str) -> bool:
1121        """Reads a model from a .mps file."""
1122        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:
1124    def import_from_lp_string(self, lp_string: str) -> bool:
1125        """Reads a model from a LP string.
1126
1127        Note that this code is very limited, and will not support any real lp.
1128        It is only intented to be use to parse test lp problems.
1129
1130        Args:
1131          lp_string: The LP string to import.
1132
1133        Returns:
1134          True if the import was successful.
1135        """
1136        return self.__helper.import_from_lp_string(lp_string)

Reads a model from a LP string.

Note that this code is very limited, and will not support any real lp. It is only intented to be use to parse test lp problems.

Arguments:
  • lp_string: The LP string to import.
Returns:

True if the import was successful.

def import_from_lp_file(self, lp_file: str) -> bool:
1138    def import_from_lp_file(self, lp_file: str) -> bool:
1139        """Reads a model from a .lp file.
1140
1141        Note that this code is very limited, and will not support any real lp.
1142        It is only intented to be use to parse test lp problems.
1143
1144        Args:
1145          lp_file: The LP file to import.
1146
1147        Returns:
1148          True if the import was successful.
1149        """
1150        return self.__helper.import_from_lp_file(lp_file)

Reads a model from a .lp file.

Note that this code is very limited, and will not support any real lp. It is only intented to be use to parse test lp problems.

Arguments:
  • lp_file: The LP file to import.
Returns:

True if the import was successful.

def import_from_proto_file(self, proto_file: str) -> bool:
1152    def import_from_proto_file(self, proto_file: str) -> bool:
1153        """Reads a model from a proto file."""
1154        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:
1156    def export_to_proto_file(self, proto_file: str) -> bool:
1157        """Writes a model to a proto file."""
1158        return self.__helper.write_model_to_proto_file(proto_file)

Writes a model to a proto file.

num_variables: int
1162    @property
1163    def num_variables(self) -> int:
1164        """Returns the number of variables in the model."""
1165        return self.__helper.num_variables()

Returns the number of variables in the model.

num_constraints: int
1167    @property
1168    def num_constraints(self) -> int:
1169        """The number of constraints in the model."""
1170        return self.__helper.num_constraints()

The number of constraints in the model.

name: str
1172    @property
1173    def name(self) -> str:
1174        """The name of the model."""
1175        return self.__helper.name()

The name of the model.

1181    @property
1182    def helper(self) -> mbh.ModelBuilderHelper:
1183        """Returns the model builder helper."""
1184        return self.__helper

Returns the model builder helper.

class Solver:
1187class Solver:
1188    """Main solver class.
1189
1190    The purpose of this class is to search for a solution to the model provided
1191    to the solve() method.
1192
1193    Once solve() is called, this class allows inspecting the solution found
1194    with the value() method, as well as general statistics about the solve
1195    procedure.
1196    """
1197
1198    def __init__(self, solver_name: str):
1199        self.__solve_helper: mbh.ModelSolverHelper = mbh.ModelSolverHelper(solver_name)
1200        self.log_callback: Optional[Callable[[str], None]] = None
1201
1202    def solver_is_supported(self) -> bool:
1203        """Checks whether the requested solver backend was found."""
1204        return self.__solve_helper.solver_is_supported()
1205
1206    # Solver backend and parameters.
1207    def set_time_limit_in_seconds(self, limit: NumberT) -> None:
1208        """Sets a time limit for the solve() call."""
1209        self.__solve_helper.set_time_limit_in_seconds(limit)
1210
1211    def set_solver_specific_parameters(self, parameters: str) -> None:
1212        """Sets parameters specific to the solver backend."""
1213        self.__solve_helper.set_solver_specific_parameters(parameters)
1214
1215    def enable_output(self, enabled: bool) -> None:
1216        """Controls the solver backend logs."""
1217        self.__solve_helper.enable_output(enabled)
1218
1219    def solve(self, model: Model) -> SolveStatus:
1220        """Solves a problem and passes each solution to the callback if not null."""
1221        if self.log_callback is not None:
1222            self.__solve_helper.set_log_callback(self.log_callback)
1223        else:
1224            self.__solve_helper.clear_log_callback()
1225        self.__solve_helper.solve(model.helper)
1226        return SolveStatus(self.__solve_helper.status())
1227
1228    def stop_search(self):
1229        """Stops the current search asynchronously."""
1230        self.__solve_helper.interrupt_solve()
1231
1232    def value(self, expr: LinearExprT) -> np.double:
1233        """Returns the value of a linear expression after solve."""
1234        if not self.__solve_helper.has_solution():
1235            return pd.NA
1236        if mbn.is_a_number(expr):
1237            return expr
1238        elif isinstance(expr, LinearExpr):
1239            return self.__solve_helper.expression_value(expr)
1240        else:
1241            raise TypeError(f"Unknown expression {type(expr).__name__!r}")
1242
1243    def values(self, variables: _IndexOrSeries) -> pd.Series:
1244        """Returns the values of the input variables.
1245
1246        If `variables` is a `pd.Index`, then the output will be indexed by the
1247        variables. If `variables` is a `pd.Series` indexed by the underlying
1248        dimensions, then the output will be indexed by the same underlying
1249        dimensions.
1250
1251        Args:
1252          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1253            get the values.
1254
1255        Returns:
1256          pd.Series: The values of all variables in the set.
1257        """
1258        if not self.__solve_helper.has_solution():
1259            return _attribute_series(func=lambda v: pd.NA, values=variables)
1260        return _attribute_series(
1261            func=lambda v: self.__solve_helper.variable_value(v.index),
1262            values=variables,
1263        )
1264
1265    def reduced_costs(self, variables: _IndexOrSeries) -> pd.Series:
1266        """Returns the reduced cost of the input variables.
1267
1268        If `variables` is a `pd.Index`, then the output will be indexed by the
1269        variables. If `variables` is a `pd.Series` indexed by the underlying
1270        dimensions, then the output will be indexed by the same underlying
1271        dimensions.
1272
1273        Args:
1274          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1275            get the values.
1276
1277        Returns:
1278          pd.Series: The reduced cost of all variables in the set.
1279        """
1280        if not self.__solve_helper.has_solution():
1281            return _attribute_series(func=lambda v: pd.NA, values=variables)
1282        return _attribute_series(
1283            func=lambda v: self.__solve_helper.reduced_cost(v.index),
1284            values=variables,
1285        )
1286
1287    def reduced_cost(self, var: Variable) -> np.double:
1288        """Returns the reduced cost of a linear expression after solve."""
1289        if not self.__solve_helper.has_solution():
1290            return pd.NA
1291        return self.__solve_helper.reduced_cost(var.index)
1292
1293    def dual_values(self, constraints: _IndexOrSeries) -> pd.Series:
1294        """Returns the dual values of the input constraints.
1295
1296        If `constraints` is a `pd.Index`, then the output will be indexed by the
1297        constraints. If `constraints` is a `pd.Series` indexed by the underlying
1298        dimensions, then the output will be indexed by the same underlying
1299        dimensions.
1300
1301        Args:
1302          constraints (Union[pd.Index, pd.Series]): The set of constraints from
1303            which to get the dual values.
1304
1305        Returns:
1306          pd.Series: The dual_values of all constraints in the set.
1307        """
1308        if not self.__solve_helper.has_solution():
1309            return _attribute_series(func=lambda v: pd.NA, values=constraints)
1310        return _attribute_series(
1311            func=lambda v: self.__solve_helper.dual_value(v.index),
1312            values=constraints,
1313        )
1314
1315    def dual_value(self, ct: LinearConstraint) -> np.double:
1316        """Returns the dual value of a linear constraint after solve."""
1317        if not self.__solve_helper.has_solution():
1318            return pd.NA
1319        return self.__solve_helper.dual_value(ct.index)
1320
1321    def activity(self, ct: LinearConstraint) -> np.double:
1322        """Returns the activity of a linear constraint after solve."""
1323        if not self.__solve_helper.has_solution():
1324            return pd.NA
1325        return self.__solve_helper.activity(ct.index)
1326
1327    @property
1328    def objective_value(self) -> np.double:
1329        """Returns the value of the objective after solve."""
1330        if not self.__solve_helper.has_solution():
1331            return pd.NA
1332        return self.__solve_helper.objective_value()
1333
1334    @property
1335    def best_objective_bound(self) -> np.double:
1336        """Returns the best lower (upper) bound found when min(max)imizing."""
1337        if not self.__solve_helper.has_solution():
1338            return pd.NA
1339        return self.__solve_helper.best_objective_bound()
1340
1341    @property
1342    def status_string(self) -> str:
1343        """Returns additional information of the last solve.
1344
1345        It can describe why the model is invalid.
1346        """
1347        return self.__solve_helper.status_string()
1348
1349    @property
1350    def wall_time(self) -> np.double:
1351        return self.__solve_helper.wall_time()
1352
1353    @property
1354    def user_time(self) -> np.double:
1355        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)
1198    def __init__(self, solver_name: str):
1199        self.__solve_helper: mbh.ModelSolverHelper = mbh.ModelSolverHelper(solver_name)
1200        self.log_callback: Optional[Callable[[str], None]] = None
log_callback: Optional[Callable[[str], NoneType]]
def solver_is_supported(self) -> bool:
1202    def solver_is_supported(self) -> bool:
1203        """Checks whether the requested solver backend was found."""
1204        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:
1207    def set_time_limit_in_seconds(self, limit: NumberT) -> None:
1208        """Sets a time limit for the solve() call."""
1209        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:
1211    def set_solver_specific_parameters(self, parameters: str) -> None:
1212        """Sets parameters specific to the solver backend."""
1213        self.__solve_helper.set_solver_specific_parameters(parameters)

Sets parameters specific to the solver backend.

def enable_output(self, enabled: bool) -> None:
1215    def enable_output(self, enabled: bool) -> None:
1216        """Controls the solver backend logs."""
1217        self.__solve_helper.enable_output(enabled)

Controls the solver backend logs.

def solve( self, model: Model) -> SolveStatus:
1219    def solve(self, model: Model) -> SolveStatus:
1220        """Solves a problem and passes each solution to the callback if not null."""
1221        if self.log_callback is not None:
1222            self.__solve_helper.set_log_callback(self.log_callback)
1223        else:
1224            self.__solve_helper.clear_log_callback()
1225        self.__solve_helper.solve(model.helper)
1226        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:
1232    def value(self, expr: LinearExprT) -> np.double:
1233        """Returns the value of a linear expression after solve."""
1234        if not self.__solve_helper.has_solution():
1235            return pd.NA
1236        if mbn.is_a_number(expr):
1237            return expr
1238        elif isinstance(expr, LinearExpr):
1239            return self.__solve_helper.expression_value(expr)
1240        else:
1241            raise TypeError(f"Unknown expression {type(expr).__name__!r}")

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:
1243    def values(self, variables: _IndexOrSeries) -> pd.Series:
1244        """Returns the values of the input variables.
1245
1246        If `variables` is a `pd.Index`, then the output will be indexed by the
1247        variables. If `variables` is a `pd.Series` indexed by the underlying
1248        dimensions, then the output will be indexed by the same underlying
1249        dimensions.
1250
1251        Args:
1252          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1253            get the values.
1254
1255        Returns:
1256          pd.Series: The values of all variables in the set.
1257        """
1258        if not self.__solve_helper.has_solution():
1259            return _attribute_series(func=lambda v: pd.NA, values=variables)
1260        return _attribute_series(
1261            func=lambda v: self.__solve_helper.variable_value(v.index),
1262            values=variables,
1263        )

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:
1265    def reduced_costs(self, variables: _IndexOrSeries) -> pd.Series:
1266        """Returns the reduced cost of the input variables.
1267
1268        If `variables` is a `pd.Index`, then the output will be indexed by the
1269        variables. If `variables` is a `pd.Series` indexed by the underlying
1270        dimensions, then the output will be indexed by the same underlying
1271        dimensions.
1272
1273        Args:
1274          variables (Union[pd.Index, pd.Series]): The set of variables from which to
1275            get the values.
1276
1277        Returns:
1278          pd.Series: The reduced cost of all variables in the set.
1279        """
1280        if not self.__solve_helper.has_solution():
1281            return _attribute_series(func=lambda v: pd.NA, values=variables)
1282        return _attribute_series(
1283            func=lambda v: self.__solve_helper.reduced_cost(v.index),
1284            values=variables,
1285        )

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:
1287    def reduced_cost(self, var: Variable) -> np.double:
1288        """Returns the reduced cost of a linear expression after solve."""
1289        if not self.__solve_helper.has_solution():
1290            return pd.NA
1291        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:
1293    def dual_values(self, constraints: _IndexOrSeries) -> pd.Series:
1294        """Returns the dual values of the input constraints.
1295
1296        If `constraints` is a `pd.Index`, then the output will be indexed by the
1297        constraints. If `constraints` is a `pd.Series` indexed by the underlying
1298        dimensions, then the output will be indexed by the same underlying
1299        dimensions.
1300
1301        Args:
1302          constraints (Union[pd.Index, pd.Series]): The set of constraints from
1303            which to get the dual values.
1304
1305        Returns:
1306          pd.Series: The dual_values of all constraints in the set.
1307        """
1308        if not self.__solve_helper.has_solution():
1309            return _attribute_series(func=lambda v: pd.NA, values=constraints)
1310        return _attribute_series(
1311            func=lambda v: self.__solve_helper.dual_value(v.index),
1312            values=constraints,
1313        )

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:
1315    def dual_value(self, ct: LinearConstraint) -> np.double:
1316        """Returns the dual value of a linear constraint after solve."""
1317        if not self.__solve_helper.has_solution():
1318            return pd.NA
1319        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:
1321    def activity(self, ct: LinearConstraint) -> np.double:
1322        """Returns the activity of a linear constraint after solve."""
1323        if not self.__solve_helper.has_solution():
1324            return pd.NA
1325        return self.__solve_helper.activity(ct.index)

Returns the activity of a linear constraint after solve.

objective_value: numpy.float64
1327    @property
1328    def objective_value(self) -> np.double:
1329        """Returns the value of the objective after solve."""
1330        if not self.__solve_helper.has_solution():
1331            return pd.NA
1332        return self.__solve_helper.objective_value()

Returns the value of the objective after solve.

best_objective_bound: numpy.float64
1334    @property
1335    def best_objective_bound(self) -> np.double:
1336        """Returns the best lower (upper) bound found when min(max)imizing."""
1337        if not self.__solve_helper.has_solution():
1338            return pd.NA
1339        return self.__solve_helper.best_objective_bound()

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

status_string: str
1341    @property
1342    def status_string(self) -> str:
1343        """Returns additional information of the last solve.
1344
1345        It can describe why the model is invalid.
1346        """
1347        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
1349    @property
1350    def wall_time(self) -> np.double:
1351        return self.__solve_helper.wall_time()
user_time: numpy.float64
1353    @property
1354    def user_time(self) -> np.double:
1355        return self.__solve_helper.user_time()
ModelBuilder = <class 'Model'>
ModelSolver = <class 'Solver'>