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 byModel.- .LinearExpr">
LinearExpr: Methods for creating constraints and the objective from large arrays of coefficients.
Other methods and functions listed are primarily used for developing OR-Tools, rather than for solving specific optimization problems.
1#!/usr/bin/env python3 2# Copyright 2010-2025 Google LLC 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Methods for building and solving model_builder models. 16 17The following two sections describe the main 18methods for building and solving those models. 19 20* [`Model`](#model_builder.Model): Methods for creating 21models, including variables and constraints. 22* [`Solver`](#model_builder.Solver): Methods for solving 23a model and evaluating solutions. 24 25Additional methods for solving Model models: 26 27* [`Constraint`](#model_builder.Constraint): A few utility methods for modifying 28 constraints created by `Model`. 29* [`LinearExpr`](#model_builder.LinearExpr): Methods for creating constraints 30 and the objective from large arrays of coefficients. 31 32Other methods and functions listed are primarily used for developing OR-Tools, 33rather than for solving specific optimization problems. 34""" 35 36from collections.abc import Callable 37import math 38import numbers 39import typing 40from typing import Optional, Union 41 42import numpy as np 43import pandas as pd 44 45from ortools.linear_solver import linear_solver_pb2 46from ortools.linear_solver.python import model_builder_helper as mbh 47from ortools.linear_solver.python import model_builder_numbers as mbn 48 49# Custom types. 50NumberT = Union[int, float, numbers.Real, np.number] 51IntegerT = Union[int, numbers.Integral, np.integer] 52LinearExprT = Union[mbh.LinearExpr, NumberT] 53ConstraintT = Union[mbh.BoundedLinearExpression, bool] 54_IndexOrSeries = Union[pd.Index, pd.Series] 55_VariableOrConstraint = Union["LinearConstraint", mbh.Variable] 56 57# Forward solve statuses. 58AffineExpr = mbh.AffineExpr 59BoundedLinearExpression = mbh.BoundedLinearExpression 60FlatExpr = mbh.FlatExpr 61LinearExpr = mbh.LinearExpr 62SolveStatus = mbh.SolveStatus 63Variable = mbh.Variable 64 65 66def _add_linear_constraint_to_helper( 67 bounded_expr: Union[bool, mbh.BoundedLinearExpression], 68 helper: mbh.ModelBuilderHelper, 69 name: Optional[str], 70): 71 """Creates a new linear constraint in the helper. 72 73 It handles boolean values (which might arise in the construction of 74 BoundedLinearExpressions). 75 76 If bounded_expr is a Boolean value, the created constraint is different. 77 In that case, the constraint will be immutable and marked as under-specified. 78 It will be always feasible or infeasible whether the value is True or False. 79 80 Args: 81 bounded_expr: The bounded expression used to create the constraint. 82 helper: The helper to create the constraint. 83 name: The name of the constraint to be created. 84 85 Returns: 86 LinearConstraint: a constraint in the helper corresponding to the input. 87 88 Raises: 89 TypeError: If constraint is an invalid type. 90 """ 91 if isinstance(bounded_expr, bool): 92 c = LinearConstraint(helper, is_under_specified=True) 93 if name is not None: 94 helper.set_constraint_name(c.index, name) 95 if bounded_expr: 96 # constraint that is always feasible: 0.0 <= nothing <= 0.0 97 helper.set_constraint_lower_bound(c.index, 0.0) 98 helper.set_constraint_upper_bound(c.index, 0.0) 99 else: 100 # constraint that is always infeasible: +oo <= nothing <= -oo 101 helper.set_constraint_lower_bound(c.index, 1) 102 helper.set_constraint_upper_bound(c.index, -1) 103 return c 104 if isinstance(bounded_expr, mbh.BoundedLinearExpression): 105 c = LinearConstraint(helper) 106 # pylint: disable=protected-access 107 helper.add_terms_to_constraint(c.index, bounded_expr.vars, bounded_expr.coeffs) 108 helper.set_constraint_lower_bound(c.index, bounded_expr.lower_bound) 109 helper.set_constraint_upper_bound(c.index, bounded_expr.upper_bound) 110 # pylint: enable=protected-access 111 if name is not None: 112 helper.set_constraint_name(c.index, name) 113 return c 114 raise TypeError(f"invalid type={type(bounded_expr).__name__!r}") 115 116 117def _add_enforced_linear_constraint_to_helper( 118 bounded_expr: Union[bool, mbh.BoundedLinearExpression], 119 helper: mbh.ModelBuilderHelper, 120 var: Variable, 121 value: bool, 122 name: Optional[str], 123): 124 """Creates a new enforced linear constraint in the helper. 125 126 It handles boolean values (which might arise in the construction of 127 BoundedLinearExpressions). 128 129 If bounded_expr is a Boolean value, the linear part of the constraint is 130 different. 131 In that case, the constraint will be immutable and marked as under-specified. 132 Its linear part will be always feasible or infeasible whether the value is 133 True or False. 134 135 Args: 136 bounded_expr: The bounded expression used to create the constraint. 137 helper: The helper to create the constraint. 138 var: the variable used in the indicator 139 value: the value used in the indicator 140 name: The name of the constraint to be created. 141 142 Returns: 143 EnforcedLinearConstraint: a constraint in the helper corresponding to the 144 input. 145 146 Raises: 147 TypeError: If constraint is an invalid type. 148 """ 149 if isinstance(bounded_expr, bool): 150 # TODO(user): create indicator variable assignment instead ? 151 c = EnforcedLinearConstraint(helper, is_under_specified=True) 152 c.indicator_variable = var 153 c.indicator_value = value 154 if name is not None: 155 helper.set_enforced_constraint_name(c.index, name) 156 if bounded_expr: 157 # constraint that is always feasible: 0.0 <= nothing <= 0.0 158 helper.set_enforced_constraint_lower_bound(c.index, 0.0) 159 helper.set_enforced_constraint_upper_bound(c.index, 0.0) 160 else: 161 # constraint that is always infeasible: +oo <= nothing <= -oo 162 helper.set_enforced_constraint_lower_bound(c.index, 1) 163 helper.set_enforced_constraint_upper_bound(c.index, -1) 164 return c 165 if isinstance(bounded_expr, mbh.BoundedLinearExpression): 166 c = EnforcedLinearConstraint(helper) 167 c.indicator_variable = var 168 c.indicator_value = value 169 helper.add_terms_to_enforced_constraint( 170 c.index, bounded_expr.vars, bounded_expr.coeffs 171 ) 172 helper.set_enforced_constraint_lower_bound(c.index, bounded_expr.lower_bound) 173 helper.set_enforced_constraint_upper_bound(c.index, bounded_expr.upper_bound) 174 if name is not None: 175 helper.set_constraint_name(c.index, name) 176 return c 177 178 raise TypeError(f"invalid type={type(bounded_expr).__name__!r}") 179 180 181class LinearConstraint: 182 """Stores a linear equation. 183 184 Example: 185 x = model.new_num_var(0, 10, 'x') 186 y = model.new_num_var(0, 10, 'y') 187 188 linear_constraint = model.add(x + 2 * y == 5) 189 """ 190 191 def __init__( 192 self, 193 helper: mbh.ModelBuilderHelper, 194 *, 195 index: Optional[IntegerT] = None, 196 is_under_specified: bool = False, 197 ) -> None: 198 """LinearConstraint constructor. 199 200 Args: 201 helper: The pybind11 ModelBuilderHelper. 202 index: If specified, recreates a wrapper to an existing linear constraint. 203 is_under_specified: indicates if the constraint was created by 204 model.add(bool). 205 """ 206 if index is None: 207 self.__index = helper.add_linear_constraint() 208 else: 209 self.__index = index 210 self.__helper: mbh.ModelBuilderHelper = helper 211 self.__is_under_specified = is_under_specified 212 213 def __hash__(self): 214 return hash((self.__helper, self.__index)) 215 216 @property 217 def index(self) -> IntegerT: 218 """Returns the index of the constraint in the helper.""" 219 return self.__index 220 221 @property 222 def helper(self) -> mbh.ModelBuilderHelper: 223 """Returns the ModelBuilderHelper instance.""" 224 return self.__helper 225 226 @property 227 def lower_bound(self) -> np.double: 228 return self.__helper.constraint_lower_bound(self.__index) 229 230 @lower_bound.setter 231 def lower_bound(self, bound: NumberT) -> None: 232 self.assert_constraint_is_well_defined() 233 self.__helper.set_constraint_lower_bound(self.__index, bound) 234 235 @property 236 def upper_bound(self) -> np.double: 237 return self.__helper.constraint_upper_bound(self.__index) 238 239 @upper_bound.setter 240 def upper_bound(self, bound: NumberT) -> None: 241 self.assert_constraint_is_well_defined() 242 self.__helper.set_constraint_upper_bound(self.__index, bound) 243 244 @property 245 def name(self) -> str: 246 constraint_name = self.__helper.constraint_name(self.__index) 247 if constraint_name: 248 return constraint_name 249 return f"linear_constraint#{self.__index}" 250 251 @name.setter 252 def name(self, name: str) -> None: 253 return self.__helper.set_constraint_name(self.__index, name) 254 255 @property 256 def is_under_specified(self) -> bool: 257 """Returns True if the constraint is under specified. 258 259 Usually, it means that it was created by model.add(False) or model.add(True) 260 The effect is that modifying the constraint will raise an exception. 261 """ 262 return self.__is_under_specified 263 264 def assert_constraint_is_well_defined(self) -> None: 265 """Raises an exception if the constraint is under specified.""" 266 if self.__is_under_specified: 267 raise ValueError( 268 f"Constraint {self.index} is under specified and cannot be modified" 269 ) 270 271 def __str__(self): 272 return self.name 273 274 def __repr__(self): 275 return ( 276 f"LinearConstraint({self.name}, lb={self.lower_bound}," 277 f" ub={self.upper_bound}," 278 f" var_indices={self.helper.constraint_var_indices(self.index)}," 279 f" coefficients={self.helper.constraint_coefficients(self.index)})" 280 ) 281 282 def set_coefficient(self, var: Variable, coeff: NumberT) -> None: 283 """Sets the coefficient of the variable in the constraint.""" 284 self.assert_constraint_is_well_defined() 285 self.__helper.set_constraint_coefficient(self.__index, var.index, coeff) 286 287 def add_term(self, var: Variable, coeff: NumberT) -> None: 288 """Adds var * coeff to the constraint.""" 289 self.assert_constraint_is_well_defined() 290 self.__helper.safe_add_term_to_constraint(self.__index, var.index, coeff) 291 292 def clear_terms(self) -> None: 293 """Clear all terms of the constraint.""" 294 self.assert_constraint_is_well_defined() 295 self.__helper.clear_constraint_terms(self.__index) 296 297 298class EnforcedLinearConstraint: 299 """Stores an enforced linear equation, also name indicator constraint. 300 301 Example: 302 x = model.new_num_var(0, 10, 'x') 303 y = model.new_num_var(0, 10, 'y') 304 z = model.new_bool_var('z') 305 306 enforced_linear_constraint = model.add_enforced(x + 2 * y == 5, z, False) 307 """ 308 309 def __init__( 310 self, 311 helper: mbh.ModelBuilderHelper, 312 *, 313 index: Optional[IntegerT] = None, 314 is_under_specified: bool = False, 315 ) -> None: 316 """EnforcedLinearConstraint constructor. 317 318 Args: 319 helper: The pybind11 ModelBuilderHelper. 320 index: If specified, recreates a wrapper to an existing linear constraint. 321 is_under_specified: indicates if the constraint was created by 322 model.add(bool). 323 """ 324 if index is None: 325 self.__index = helper.add_enforced_linear_constraint() 326 else: 327 if not helper.is_enforced_linear_constraint(index): 328 raise ValueError( 329 f"the given index {index} does not refer to an enforced linear" 330 " constraint" 331 ) 332 333 self.__index = index 334 self.__helper: mbh.ModelBuilderHelper = helper 335 self.__is_under_specified = is_under_specified 336 337 @property 338 def index(self) -> IntegerT: 339 """Returns the index of the constraint in the helper.""" 340 return self.__index 341 342 @property 343 def helper(self) -> mbh.ModelBuilderHelper: 344 """Returns the ModelBuilderHelper instance.""" 345 return self.__helper 346 347 @property 348 def lower_bound(self) -> np.double: 349 return self.__helper.enforced_constraint_lower_bound(self.__index) 350 351 @lower_bound.setter 352 def lower_bound(self, bound: NumberT) -> None: 353 self.assert_constraint_is_well_defined() 354 self.__helper.set_enforced_constraint_lower_bound(self.__index, bound) 355 356 @property 357 def upper_bound(self) -> np.double: 358 return self.__helper.enforced_constraint_upper_bound(self.__index) 359 360 @upper_bound.setter 361 def upper_bound(self, bound: NumberT) -> None: 362 self.assert_constraint_is_well_defined() 363 self.__helper.set_enforced_constraint_upper_bound(self.__index, bound) 364 365 @property 366 def indicator_variable(self) -> "Variable": 367 enforcement_var_index = ( 368 self.__helper.enforced_constraint_indicator_variable_index(self.__index) 369 ) 370 return Variable(self.__helper, enforcement_var_index) 371 372 @indicator_variable.setter 373 def indicator_variable(self, var: "Variable") -> None: 374 self.__helper.set_enforced_constraint_indicator_variable_index( 375 self.__index, var.index 376 ) 377 378 @property 379 def indicator_value(self) -> bool: 380 return self.__helper.enforced_constraint_indicator_value(self.__index) 381 382 @indicator_value.setter 383 def indicator_value(self, value: bool) -> None: 384 self.__helper.set_enforced_constraint_indicator_value(self.__index, value) 385 386 @property 387 def name(self) -> str: 388 constraint_name = self.__helper.enforced_constraint_name(self.__index) 389 if constraint_name: 390 return constraint_name 391 return f"enforced_linear_constraint#{self.__index}" 392 393 @name.setter 394 def name(self, name: str) -> None: 395 return self.__helper.set_enforced_constraint_name(self.__index, name) 396 397 @property 398 def is_under_specified(self) -> bool: 399 """Returns True if the constraint is under specified. 400 401 Usually, it means that it was created by model.add(False) or model.add(True) 402 The effect is that modifying the constraint will raise an exception. 403 """ 404 return self.__is_under_specified 405 406 def assert_constraint_is_well_defined(self) -> None: 407 """Raises an exception if the constraint is under specified.""" 408 if self.__is_under_specified: 409 raise ValueError( 410 f"Constraint {self.index} is under specified and cannot be modified" 411 ) 412 413 def __str__(self): 414 return self.name 415 416 def __repr__(self): 417 return ( 418 f"EnforcedLinearConstraint({self.name}, lb={self.lower_bound}," 419 f" ub={self.upper_bound}," 420 f" var_indices={self.helper.enforced_constraint_var_indices(self.index)}," 421 f" coefficients={self.helper.enforced_constraint_coefficients(self.index)}," 422 f" indicator_variable={self.indicator_variable}" 423 f" indicator_value={self.indicator_value})" 424 ) 425 426 def set_coefficient(self, var: Variable, coeff: NumberT) -> None: 427 """Sets the coefficient of the variable in the constraint.""" 428 self.assert_constraint_is_well_defined() 429 self.__helper.set_enforced_constraint_coefficient( 430 self.__index, var.index, coeff 431 ) 432 433 def add_term(self, var: Variable, coeff: NumberT) -> None: 434 """Adds var * coeff to the constraint.""" 435 self.assert_constraint_is_well_defined() 436 self.__helper.safe_add_term_to_enforced_constraint( 437 self.__index, var.index, coeff 438 ) 439 440 def clear_terms(self) -> None: 441 """Clear all terms of the constraint.""" 442 self.assert_constraint_is_well_defined() 443 self.__helper.clear_enforced_constraint_terms(self.__index) 444 445 446class Model: 447 """Methods for building a linear model. 448 449 Methods beginning with: 450 451 * ```new_``` create integer, boolean, or interval variables. 452 * ```add_``` create new constraints and add them to the model. 453 """ 454 455 def __init__(self): 456 self.__helper: mbh.ModelBuilderHelper = mbh.ModelBuilderHelper() 457 458 def clone(self) -> "Model": 459 """Returns a clone of the current model.""" 460 clone = Model() 461 clone.helper.overwrite_model(self.helper) 462 return clone 463 464 @typing.overload 465 def _get_linear_constraints(self, constraints: Optional[pd.Index]) -> pd.Index: ... 466 467 @typing.overload 468 def _get_linear_constraints(self, constraints: pd.Series) -> pd.Series: ... 469 470 def _get_linear_constraints( 471 self, constraints: Optional[_IndexOrSeries] = None 472 ) -> _IndexOrSeries: 473 if constraints is None: 474 return self.get_linear_constraints() 475 return constraints 476 477 @typing.overload 478 def _get_variables(self, variables: Optional[pd.Index]) -> pd.Index: ... 479 480 @typing.overload 481 def _get_variables(self, variables: pd.Series) -> pd.Series: ... 482 483 def _get_variables( 484 self, variables: Optional[_IndexOrSeries] = None 485 ) -> _IndexOrSeries: 486 if variables is None: 487 return self.get_variables() 488 return variables 489 490 def get_linear_constraints(self) -> pd.Index: 491 """Gets all linear constraints in the model.""" 492 return pd.Index( 493 [self.linear_constraint_from_index(i) for i in range(self.num_constraints)], 494 name="linear_constraint", 495 ) 496 497 def get_linear_constraint_expressions( 498 self, constraints: Optional[_IndexOrSeries] = None 499 ) -> pd.Series: 500 """Gets the expressions of all linear constraints in the set. 501 502 If `constraints` is a `pd.Index`, then the output will be indexed by the 503 constraints. If `constraints` is a `pd.Series` indexed by the underlying 504 dimensions, then the output will be indexed by the same underlying 505 dimensions. 506 507 Args: 508 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 509 constraints from which to get the expressions. If unspecified, all 510 linear constraints will be in scope. 511 512 Returns: 513 pd.Series: The expressions of all linear constraints in the set. 514 """ 515 return _attribute_series( 516 # pylint: disable=g-long-lambda 517 func=lambda c: mbh.FlatExpr( 518 # pylint: disable=g-complex-comprehension 519 [ 520 Variable(self.__helper, var_id) 521 for var_id in c.helper.constraint_var_indices(c.index) 522 ], 523 c.helper.constraint_coefficients(c.index), 524 0.0, 525 ), 526 values=self._get_linear_constraints(constraints), 527 ) 528 529 def get_linear_constraint_lower_bounds( 530 self, constraints: Optional[_IndexOrSeries] = None 531 ) -> pd.Series: 532 """Gets the lower bounds of all linear constraints in the set. 533 534 If `constraints` is a `pd.Index`, then the output will be indexed by the 535 constraints. If `constraints` is a `pd.Series` indexed by the underlying 536 dimensions, then the output will be indexed by the same underlying 537 dimensions. 538 539 Args: 540 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 541 constraints from which to get the lower bounds. If unspecified, all 542 linear constraints will be in scope. 543 544 Returns: 545 pd.Series: The lower bounds of all linear constraints in the set. 546 """ 547 return _attribute_series( 548 func=lambda c: c.lower_bound, # pylint: disable=protected-access 549 values=self._get_linear_constraints(constraints), 550 ) 551 552 def get_linear_constraint_upper_bounds( 553 self, constraints: Optional[_IndexOrSeries] = None 554 ) -> pd.Series: 555 """Gets the upper bounds of all linear constraints in the set. 556 557 If `constraints` is a `pd.Index`, then the output will be indexed by the 558 constraints. If `constraints` is a `pd.Series` indexed by the underlying 559 dimensions, then the output will be indexed by the same underlying 560 dimensions. 561 562 Args: 563 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 564 constraints. If unspecified, all linear constraints will be in scope. 565 566 Returns: 567 pd.Series: The upper bounds of all linear constraints in the set. 568 """ 569 return _attribute_series( 570 func=lambda c: c.upper_bound, # pylint: disable=protected-access 571 values=self._get_linear_constraints(constraints), 572 ) 573 574 def get_variables(self) -> pd.Index: 575 """Gets all variables in the model.""" 576 return pd.Index( 577 [self.var_from_index(i) for i in range(self.num_variables)], 578 name="variable", 579 ) 580 581 def get_variable_lower_bounds( 582 self, variables: Optional[_IndexOrSeries] = None 583 ) -> pd.Series: 584 """Gets the lower bounds of all variables in the set. 585 586 If `variables` is a `pd.Index`, then the output will be indexed by the 587 variables. If `variables` is a `pd.Series` indexed by the underlying 588 dimensions, then the output will be indexed by the same underlying 589 dimensions. 590 591 Args: 592 variables (Union[pd.Index, pd.Series]): Optional. The set of variables 593 from which to get the lower bounds. If unspecified, all variables will 594 be in scope. 595 596 Returns: 597 pd.Series: The lower bounds of all variables in the set. 598 """ 599 return _attribute_series( 600 func=lambda v: v.lower_bound, # pylint: disable=protected-access 601 values=self._get_variables(variables), 602 ) 603 604 def get_variable_upper_bounds( 605 self, variables: Optional[_IndexOrSeries] = None 606 ) -> pd.Series: 607 """Gets the upper bounds of all variables in the set. 608 609 Args: 610 variables (Union[pd.Index, pd.Series]): Optional. The set of variables 611 from which to get the upper bounds. If unspecified, all variables will 612 be in scope. 613 614 Returns: 615 pd.Series: The upper bounds of all variables in the set. 616 """ 617 return _attribute_series( 618 func=lambda v: v.upper_bound, # pylint: disable=protected-access 619 values=self._get_variables(variables), 620 ) 621 622 # Integer variable. 623 624 def new_var( 625 self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str] 626 ) -> Variable: 627 """Create an integer variable with domain [lb, ub]. 628 629 Args: 630 lb: Lower bound of the variable. 631 ub: Upper bound of the variable. 632 is_integer: Indicates if the variable must take integral values. 633 name: The name of the variable. 634 635 Returns: 636 a variable whose domain is [lb, ub]. 637 """ 638 if name: 639 return Variable(self.__helper, lb, ub, is_integer, name) 640 else: 641 return Variable(self.__helper, lb, ub, is_integer) 642 643 def new_int_var( 644 self, lb: NumberT, ub: NumberT, name: Optional[str] = None 645 ) -> Variable: 646 """Create an integer variable with domain [lb, ub]. 647 648 Args: 649 lb: Lower bound of the variable. 650 ub: Upper bound of the variable. 651 name: The name of the variable. 652 653 Returns: 654 a variable whose domain is [lb, ub]. 655 """ 656 657 return self.new_var(lb, ub, True, name) 658 659 def new_num_var( 660 self, lb: NumberT, ub: NumberT, name: Optional[str] = None 661 ) -> Variable: 662 """Create an integer variable with domain [lb, ub]. 663 664 Args: 665 lb: Lower bound of the variable. 666 ub: Upper bound of the variable. 667 name: The name of the variable. 668 669 Returns: 670 a variable whose domain is [lb, ub]. 671 """ 672 673 return self.new_var(lb, ub, False, name) 674 675 def new_bool_var(self, name: Optional[str] = None) -> Variable: 676 """Creates a 0-1 variable with the given name.""" 677 return self.new_var(0, 1, True, name) # 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
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)
__init__(self: ortools.linear_solver.python.model_builder_helper.AffineExpr, arg0: ortools.linear_solver.python.model_builder_helper.LinearExpr, arg1: float, arg2: float) -> None
Inherited Members
__init__(args, *kwargs) Overloaded function.
__init__(self: ortools.linear_solver.python.model_builder_helper.BoundedLinearExpression, arg0: ortools.linear_solver.python.model_builder_helper.LinearExpr, arg1: float, arg2: float) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.BoundedLinearExpression, arg0: ortools.linear_solver.python.model_builder_helper.LinearExpr, arg1: ortools.linear_solver.python.model_builder_helper.LinearExpr, arg2: float, arg3: float) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.BoundedLinearExpression, arg0: ortools.linear_solver.python.model_builder_helper.LinearExpr, arg1: int, arg2: int) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.BoundedLinearExpression, arg0: ortools.linear_solver.python.model_builder_helper.LinearExpr, arg1: ortools.linear_solver.python.model_builder_helper.LinearExpr, arg2: int, arg3: int) -> None
(arg0: ortools.linear_solver.python.model_builder_helper.BoundedLinearExpression) -> list[float]
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)
__init__(args, *kwargs) Overloaded function.
__init__(self: ortools.linear_solver.python.model_builder_helper.FlatExpr, arg0: ortools.linear_solver.python.model_builder_helper.LinearExpr) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.FlatExpr, arg0: ortools.linear_solver.python.model_builder_helper.LinearExpr, arg1: ortools.linear_solver.python.model_builder_helper.LinearExpr) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.FlatExpr, arg0: list[operations_research::mb::Variable], arg1: list[float], arg2: float) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.FlatExpr, arg0: float) -> None
(arg0: ortools.linear_solver.python.model_builder_helper.FlatExpr) -> list[operations_research::mb::Variable]
variable_indices(self: ortools.linear_solver.python.model_builder_helper.FlatExpr) -> list[int]
Inherited Members
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)
sum(args, *kwargs) -> ortools.linear_solver.python.model_builder_helper.LinearExpr
Creates sum(expressions) [+ constant].
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].
term(args, *kwargs) Overloaded function.
- term(expr: ortools.linear_solver.python.model_builder_helper.LinearExpr, coeff: float) -> ortools.linear_solver.python.model_builder_helper.LinearExpr
Returns expr * coeff.
- 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].
- term(value: float, coeff: float, *, constant: float) -> ortools.linear_solver.python.model_builder_helper.LinearExpr
Returns value * coeff [+ constant].
affine(args, *kwargs) Overloaded function.
- 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.
- affine(value: float, coeff: float, constant: float = 0.0) -> ortools.linear_solver.python.model_builder_helper.LinearExpr
Returns value * coeff + constant.
constant(value: float) -> ortools.linear_solver.python.model_builder_helper.LinearExpr
Returns a constant linear expression.
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
__init__(self: ortools.linear_solver.python.model_builder_helper.SolveStatus, value: int) -> None
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.
__init__(args, *kwargs) Overloaded function.
__init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: int) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: float, arg2: float, arg3: bool) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: float, arg2: float, arg3: bool, arg4: str) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: int, arg2: int, arg3: bool) -> None
__init__(self: ortools.linear_solver.python.model_builder_helper.Variable, arg0: operations_research::mb::ModelBuilderHelper, arg1: int, arg2: int, arg3: bool, arg4: str) -> None
(arg0: ortools.linear_solver.python.model_builder_helper.Variable) -> operations_research::mb::ModelBuilderHelper
Inherited Members
182class LinearConstraint: 183 """Stores a linear equation. 184 185 Example: 186 x = model.new_num_var(0, 10, 'x') 187 y = model.new_num_var(0, 10, 'y') 188 189 linear_constraint = model.add(x + 2 * y == 5) 190 """ 191 192 def __init__( 193 self, 194 helper: mbh.ModelBuilderHelper, 195 *, 196 index: Optional[IntegerT] = None, 197 is_under_specified: bool = False, 198 ) -> None: 199 """LinearConstraint constructor. 200 201 Args: 202 helper: The pybind11 ModelBuilderHelper. 203 index: If specified, recreates a wrapper to an existing linear constraint. 204 is_under_specified: indicates if the constraint was created by 205 model.add(bool). 206 """ 207 if index is None: 208 self.__index = helper.add_linear_constraint() 209 else: 210 self.__index = index 211 self.__helper: mbh.ModelBuilderHelper = helper 212 self.__is_under_specified = is_under_specified 213 214 def __hash__(self): 215 return hash((self.__helper, self.__index)) 216 217 @property 218 def index(self) -> IntegerT: 219 """Returns the index of the constraint in the helper.""" 220 return self.__index 221 222 @property 223 def helper(self) -> mbh.ModelBuilderHelper: 224 """Returns the ModelBuilderHelper instance.""" 225 return self.__helper 226 227 @property 228 def lower_bound(self) -> np.double: 229 return self.__helper.constraint_lower_bound(self.__index) 230 231 @lower_bound.setter 232 def lower_bound(self, bound: NumberT) -> None: 233 self.assert_constraint_is_well_defined() 234 self.__helper.set_constraint_lower_bound(self.__index, bound) 235 236 @property 237 def upper_bound(self) -> np.double: 238 return self.__helper.constraint_upper_bound(self.__index) 239 240 @upper_bound.setter 241 def upper_bound(self, bound: NumberT) -> None: 242 self.assert_constraint_is_well_defined() 243 self.__helper.set_constraint_upper_bound(self.__index, bound) 244 245 @property 246 def name(self) -> str: 247 constraint_name = self.__helper.constraint_name(self.__index) 248 if constraint_name: 249 return constraint_name 250 return f"linear_constraint#{self.__index}" 251 252 @name.setter 253 def name(self, name: str) -> None: 254 return self.__helper.set_constraint_name(self.__index, name) 255 256 @property 257 def is_under_specified(self) -> bool: 258 """Returns True if the constraint is under specified. 259 260 Usually, it means that it was created by model.add(False) or model.add(True) 261 The effect is that modifying the constraint will raise an exception. 262 """ 263 return self.__is_under_specified 264 265 def assert_constraint_is_well_defined(self) -> None: 266 """Raises an exception if the constraint is under specified.""" 267 if self.__is_under_specified: 268 raise ValueError( 269 f"Constraint {self.index} is under specified and cannot be modified" 270 ) 271 272 def __str__(self): 273 return self.name 274 275 def __repr__(self): 276 return ( 277 f"LinearConstraint({self.name}, lb={self.lower_bound}," 278 f" ub={self.upper_bound}," 279 f" var_indices={self.helper.constraint_var_indices(self.index)}," 280 f" coefficients={self.helper.constraint_coefficients(self.index)})" 281 ) 282 283 def set_coefficient(self, var: Variable, coeff: NumberT) -> None: 284 """Sets the coefficient of the variable in the constraint.""" 285 self.assert_constraint_is_well_defined() 286 self.__helper.set_constraint_coefficient(self.__index, var.index, coeff) 287 288 def add_term(self, var: Variable, coeff: NumberT) -> None: 289 """Adds var * coeff to the constraint.""" 290 self.assert_constraint_is_well_defined() 291 self.__helper.safe_add_term_to_constraint(self.__index, var.index, coeff) 292 293 def clear_terms(self) -> None: 294 """Clear all terms of the constraint.""" 295 self.assert_constraint_is_well_defined() 296 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)
192 def __init__( 193 self, 194 helper: mbh.ModelBuilderHelper, 195 *, 196 index: Optional[IntegerT] = None, 197 is_under_specified: bool = False, 198 ) -> None: 199 """LinearConstraint constructor. 200 201 Args: 202 helper: The pybind11 ModelBuilderHelper. 203 index: If specified, recreates a wrapper to an existing linear constraint. 204 is_under_specified: indicates if the constraint was created by 205 model.add(bool). 206 """ 207 if index is None: 208 self.__index = helper.add_linear_constraint() 209 else: 210 self.__index = index 211 self.__helper: mbh.ModelBuilderHelper = helper 212 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).
217 @property 218 def index(self) -> IntegerT: 219 """Returns the index of the constraint in the helper.""" 220 return self.__index
Returns the index of the constraint in the helper.
222 @property 223 def helper(self) -> mbh.ModelBuilderHelper: 224 """Returns the ModelBuilderHelper instance.""" 225 return self.__helper
Returns the ModelBuilderHelper instance.
256 @property 257 def is_under_specified(self) -> bool: 258 """Returns True if the constraint is under specified. 259 260 Usually, it means that it was created by model.add(False) or model.add(True) 261 The effect is that modifying the constraint will raise an exception. 262 """ 263 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.
265 def assert_constraint_is_well_defined(self) -> None: 266 """Raises an exception if the constraint is under specified.""" 267 if self.__is_under_specified: 268 raise ValueError( 269 f"Constraint {self.index} is under specified and cannot be modified" 270 )
Raises an exception if the constraint is under specified.
283 def set_coefficient(self, var: Variable, coeff: NumberT) -> None: 284 """Sets the coefficient of the variable in the constraint.""" 285 self.assert_constraint_is_well_defined() 286 self.__helper.set_constraint_coefficient(self.__index, var.index, coeff)
Sets the coefficient of the variable in the constraint.
299class EnforcedLinearConstraint: 300 """Stores an enforced linear equation, also name indicator constraint. 301 302 Example: 303 x = model.new_num_var(0, 10, 'x') 304 y = model.new_num_var(0, 10, 'y') 305 z = model.new_bool_var('z') 306 307 enforced_linear_constraint = model.add_enforced(x + 2 * y == 5, z, False) 308 """ 309 310 def __init__( 311 self, 312 helper: mbh.ModelBuilderHelper, 313 *, 314 index: Optional[IntegerT] = None, 315 is_under_specified: bool = False, 316 ) -> None: 317 """EnforcedLinearConstraint constructor. 318 319 Args: 320 helper: The pybind11 ModelBuilderHelper. 321 index: If specified, recreates a wrapper to an existing linear constraint. 322 is_under_specified: indicates if the constraint was created by 323 model.add(bool). 324 """ 325 if index is None: 326 self.__index = helper.add_enforced_linear_constraint() 327 else: 328 if not helper.is_enforced_linear_constraint(index): 329 raise ValueError( 330 f"the given index {index} does not refer to an enforced linear" 331 " constraint" 332 ) 333 334 self.__index = index 335 self.__helper: mbh.ModelBuilderHelper = helper 336 self.__is_under_specified = is_under_specified 337 338 @property 339 def index(self) -> IntegerT: 340 """Returns the index of the constraint in the helper.""" 341 return self.__index 342 343 @property 344 def helper(self) -> mbh.ModelBuilderHelper: 345 """Returns the ModelBuilderHelper instance.""" 346 return self.__helper 347 348 @property 349 def lower_bound(self) -> np.double: 350 return self.__helper.enforced_constraint_lower_bound(self.__index) 351 352 @lower_bound.setter 353 def lower_bound(self, bound: NumberT) -> None: 354 self.assert_constraint_is_well_defined() 355 self.__helper.set_enforced_constraint_lower_bound(self.__index, bound) 356 357 @property 358 def upper_bound(self) -> np.double: 359 return self.__helper.enforced_constraint_upper_bound(self.__index) 360 361 @upper_bound.setter 362 def upper_bound(self, bound: NumberT) -> None: 363 self.assert_constraint_is_well_defined() 364 self.__helper.set_enforced_constraint_upper_bound(self.__index, bound) 365 366 @property 367 def indicator_variable(self) -> "Variable": 368 enforcement_var_index = ( 369 self.__helper.enforced_constraint_indicator_variable_index(self.__index) 370 ) 371 return Variable(self.__helper, enforcement_var_index) 372 373 @indicator_variable.setter 374 def indicator_variable(self, var: "Variable") -> None: 375 self.__helper.set_enforced_constraint_indicator_variable_index( 376 self.__index, var.index 377 ) 378 379 @property 380 def indicator_value(self) -> bool: 381 return self.__helper.enforced_constraint_indicator_value(self.__index) 382 383 @indicator_value.setter 384 def indicator_value(self, value: bool) -> None: 385 self.__helper.set_enforced_constraint_indicator_value(self.__index, value) 386 387 @property 388 def name(self) -> str: 389 constraint_name = self.__helper.enforced_constraint_name(self.__index) 390 if constraint_name: 391 return constraint_name 392 return f"enforced_linear_constraint#{self.__index}" 393 394 @name.setter 395 def name(self, name: str) -> None: 396 return self.__helper.set_enforced_constraint_name(self.__index, name) 397 398 @property 399 def is_under_specified(self) -> bool: 400 """Returns True if the constraint is under specified. 401 402 Usually, it means that it was created by model.add(False) or model.add(True) 403 The effect is that modifying the constraint will raise an exception. 404 """ 405 return self.__is_under_specified 406 407 def assert_constraint_is_well_defined(self) -> None: 408 """Raises an exception if the constraint is under specified.""" 409 if self.__is_under_specified: 410 raise ValueError( 411 f"Constraint {self.index} is under specified and cannot be modified" 412 ) 413 414 def __str__(self): 415 return self.name 416 417 def __repr__(self): 418 return ( 419 f"EnforcedLinearConstraint({self.name}, lb={self.lower_bound}," 420 f" ub={self.upper_bound}," 421 f" var_indices={self.helper.enforced_constraint_var_indices(self.index)}," 422 f" coefficients={self.helper.enforced_constraint_coefficients(self.index)}," 423 f" indicator_variable={self.indicator_variable}" 424 f" indicator_value={self.indicator_value})" 425 ) 426 427 def set_coefficient(self, var: Variable, coeff: NumberT) -> None: 428 """Sets the coefficient of the variable in the constraint.""" 429 self.assert_constraint_is_well_defined() 430 self.__helper.set_enforced_constraint_coefficient( 431 self.__index, var.index, coeff 432 ) 433 434 def add_term(self, var: Variable, coeff: NumberT) -> None: 435 """Adds var * coeff to the constraint.""" 436 self.assert_constraint_is_well_defined() 437 self.__helper.safe_add_term_to_enforced_constraint( 438 self.__index, var.index, coeff 439 ) 440 441 def clear_terms(self) -> None: 442 """Clear all terms of the constraint.""" 443 self.assert_constraint_is_well_defined() 444 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)
310 def __init__( 311 self, 312 helper: mbh.ModelBuilderHelper, 313 *, 314 index: Optional[IntegerT] = None, 315 is_under_specified: bool = False, 316 ) -> None: 317 """EnforcedLinearConstraint constructor. 318 319 Args: 320 helper: The pybind11 ModelBuilderHelper. 321 index: If specified, recreates a wrapper to an existing linear constraint. 322 is_under_specified: indicates if the constraint was created by 323 model.add(bool). 324 """ 325 if index is None: 326 self.__index = helper.add_enforced_linear_constraint() 327 else: 328 if not helper.is_enforced_linear_constraint(index): 329 raise ValueError( 330 f"the given index {index} does not refer to an enforced linear" 331 " constraint" 332 ) 333 334 self.__index = index 335 self.__helper: mbh.ModelBuilderHelper = helper 336 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).
338 @property 339 def index(self) -> IntegerT: 340 """Returns the index of the constraint in the helper.""" 341 return self.__index
Returns the index of the constraint in the helper.
343 @property 344 def helper(self) -> mbh.ModelBuilderHelper: 345 """Returns the ModelBuilderHelper instance.""" 346 return self.__helper
Returns the ModelBuilderHelper instance.
398 @property 399 def is_under_specified(self) -> bool: 400 """Returns True if the constraint is under specified. 401 402 Usually, it means that it was created by model.add(False) or model.add(True) 403 The effect is that modifying the constraint will raise an exception. 404 """ 405 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.
407 def assert_constraint_is_well_defined(self) -> None: 408 """Raises an exception if the constraint is under specified.""" 409 if self.__is_under_specified: 410 raise ValueError( 411 f"Constraint {self.index} is under specified and cannot be modified" 412 )
Raises an exception if the constraint is under specified.
427 def set_coefficient(self, var: Variable, coeff: NumberT) -> None: 428 """Sets the coefficient of the variable in the constraint.""" 429 self.assert_constraint_is_well_defined() 430 self.__helper.set_enforced_constraint_coefficient( 431 self.__index, var.index, coeff 432 )
Sets the coefficient of the variable in the constraint.
447class Model: 448 """Methods for building a linear model. 449 450 Methods beginning with: 451 452 * ```new_``` create integer, boolean, or interval variables. 453 * ```add_``` create new constraints and add them to the model. 454 """ 455 456 def __init__(self): 457 self.__helper: mbh.ModelBuilderHelper = mbh.ModelBuilderHelper() 458 459 def clone(self) -> "Model": 460 """Returns a clone of the current model.""" 461 clone = Model() 462 clone.helper.overwrite_model(self.helper) 463 return clone 464 465 @typing.overload 466 def _get_linear_constraints(self, constraints: Optional[pd.Index]) -> pd.Index: ... 467 468 @typing.overload 469 def _get_linear_constraints(self, constraints: pd.Series) -> pd.Series: ... 470 471 def _get_linear_constraints( 472 self, constraints: Optional[_IndexOrSeries] = None 473 ) -> _IndexOrSeries: 474 if constraints is None: 475 return self.get_linear_constraints() 476 return constraints 477 478 @typing.overload 479 def _get_variables(self, variables: Optional[pd.Index]) -> pd.Index: ... 480 481 @typing.overload 482 def _get_variables(self, variables: pd.Series) -> pd.Series: ... 483 484 def _get_variables( 485 self, variables: Optional[_IndexOrSeries] = None 486 ) -> _IndexOrSeries: 487 if variables is None: 488 return self.get_variables() 489 return variables 490 491 def get_linear_constraints(self) -> pd.Index: 492 """Gets all linear constraints in the model.""" 493 return pd.Index( 494 [self.linear_constraint_from_index(i) for i in range(self.num_constraints)], 495 name="linear_constraint", 496 ) 497 498 def get_linear_constraint_expressions( 499 self, constraints: Optional[_IndexOrSeries] = None 500 ) -> pd.Series: 501 """Gets the expressions of all linear constraints in the set. 502 503 If `constraints` is a `pd.Index`, then the output will be indexed by the 504 constraints. If `constraints` is a `pd.Series` indexed by the underlying 505 dimensions, then the output will be indexed by the same underlying 506 dimensions. 507 508 Args: 509 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 510 constraints from which to get the expressions. If unspecified, all 511 linear constraints will be in scope. 512 513 Returns: 514 pd.Series: The expressions of all linear constraints in the set. 515 """ 516 return _attribute_series( 517 # pylint: disable=g-long-lambda 518 func=lambda c: mbh.FlatExpr( 519 # pylint: disable=g-complex-comprehension 520 [ 521 Variable(self.__helper, var_id) 522 for var_id in c.helper.constraint_var_indices(c.index) 523 ], 524 c.helper.constraint_coefficients(c.index), 525 0.0, 526 ), 527 values=self._get_linear_constraints(constraints), 528 ) 529 530 def get_linear_constraint_lower_bounds( 531 self, constraints: Optional[_IndexOrSeries] = None 532 ) -> pd.Series: 533 """Gets the lower bounds of all linear constraints in the set. 534 535 If `constraints` is a `pd.Index`, then the output will be indexed by the 536 constraints. If `constraints` is a `pd.Series` indexed by the underlying 537 dimensions, then the output will be indexed by the same underlying 538 dimensions. 539 540 Args: 541 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 542 constraints from which to get the lower bounds. If unspecified, all 543 linear constraints will be in scope. 544 545 Returns: 546 pd.Series: The lower bounds of all linear constraints in the set. 547 """ 548 return _attribute_series( 549 func=lambda c: c.lower_bound, # pylint: disable=protected-access 550 values=self._get_linear_constraints(constraints), 551 ) 552 553 def get_linear_constraint_upper_bounds( 554 self, constraints: Optional[_IndexOrSeries] = None 555 ) -> pd.Series: 556 """Gets the upper bounds of all linear constraints in the set. 557 558 If `constraints` is a `pd.Index`, then the output will be indexed by the 559 constraints. If `constraints` is a `pd.Series` indexed by the underlying 560 dimensions, then the output will be indexed by the same underlying 561 dimensions. 562 563 Args: 564 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 565 constraints. If unspecified, all linear constraints will be in scope. 566 567 Returns: 568 pd.Series: The upper bounds of all linear constraints in the set. 569 """ 570 return _attribute_series( 571 func=lambda c: c.upper_bound, # pylint: disable=protected-access 572 values=self._get_linear_constraints(constraints), 573 ) 574 575 def get_variables(self) -> pd.Index: 576 """Gets all variables in the model.""" 577 return pd.Index( 578 [self.var_from_index(i) for i in range(self.num_variables)], 579 name="variable", 580 ) 581 582 def get_variable_lower_bounds( 583 self, variables: Optional[_IndexOrSeries] = None 584 ) -> pd.Series: 585 """Gets the lower bounds of all variables in the set. 586 587 If `variables` is a `pd.Index`, then the output will be indexed by the 588 variables. If `variables` is a `pd.Series` indexed by the underlying 589 dimensions, then the output will be indexed by the same underlying 590 dimensions. 591 592 Args: 593 variables (Union[pd.Index, pd.Series]): Optional. The set of variables 594 from which to get the lower bounds. If unspecified, all variables will 595 be in scope. 596 597 Returns: 598 pd.Series: The lower bounds of all variables in the set. 599 """ 600 return _attribute_series( 601 func=lambda v: v.lower_bound, # pylint: disable=protected-access 602 values=self._get_variables(variables), 603 ) 604 605 def get_variable_upper_bounds( 606 self, variables: Optional[_IndexOrSeries] = None 607 ) -> pd.Series: 608 """Gets the upper bounds of all variables in the set. 609 610 Args: 611 variables (Union[pd.Index, pd.Series]): Optional. The set of variables 612 from which to get the upper bounds. If unspecified, all variables will 613 be in scope. 614 615 Returns: 616 pd.Series: The upper bounds of all variables in the set. 617 """ 618 return _attribute_series( 619 func=lambda v: v.upper_bound, # pylint: disable=protected-access 620 values=self._get_variables(variables), 621 ) 622 623 # Integer variable. 624 625 def new_var( 626 self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str] 627 ) -> Variable: 628 """Create an integer variable with domain [lb, ub]. 629 630 Args: 631 lb: Lower bound of the variable. 632 ub: Upper bound of the variable. 633 is_integer: Indicates if the variable must take integral values. 634 name: The name of the variable. 635 636 Returns: 637 a variable whose domain is [lb, ub]. 638 """ 639 if name: 640 return Variable(self.__helper, lb, ub, is_integer, name) 641 else: 642 return Variable(self.__helper, lb, ub, is_integer) 643 644 def new_int_var( 645 self, lb: NumberT, ub: NumberT, name: Optional[str] = None 646 ) -> Variable: 647 """Create an integer variable with domain [lb, ub]. 648 649 Args: 650 lb: Lower bound of the variable. 651 ub: Upper bound of the variable. 652 name: The name of the variable. 653 654 Returns: 655 a variable whose domain is [lb, ub]. 656 """ 657 658 return self.new_var(lb, ub, True, name) 659 660 def new_num_var( 661 self, lb: NumberT, ub: NumberT, name: Optional[str] = None 662 ) -> Variable: 663 """Create an integer variable with domain [lb, ub]. 664 665 Args: 666 lb: Lower bound of the variable. 667 ub: Upper bound of the variable. 668 name: The name of the variable. 669 670 Returns: 671 a variable whose domain is [lb, ub]. 672 """ 673 674 return self.new_var(lb, ub, False, name) 675 676 def new_bool_var(self, name: Optional[str] = None) -> Variable: 677 """Creates a 0-1 variable with the given name.""" 678 return self.new_var(0, 1, True, name) # 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.
459 def clone(self) -> "Model": 460 """Returns a clone of the current model.""" 461 clone = Model() 462 clone.helper.overwrite_model(self.helper) 463 return clone
Returns a clone of the current model.
491 def get_linear_constraints(self) -> pd.Index: 492 """Gets all linear constraints in the model.""" 493 return pd.Index( 494 [self.linear_constraint_from_index(i) for i in range(self.num_constraints)], 495 name="linear_constraint", 496 )
Gets all linear constraints in the model.
498 def get_linear_constraint_expressions( 499 self, constraints: Optional[_IndexOrSeries] = None 500 ) -> pd.Series: 501 """Gets the expressions of all linear constraints in the set. 502 503 If `constraints` is a `pd.Index`, then the output will be indexed by the 504 constraints. If `constraints` is a `pd.Series` indexed by the underlying 505 dimensions, then the output will be indexed by the same underlying 506 dimensions. 507 508 Args: 509 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 510 constraints from which to get the expressions. If unspecified, all 511 linear constraints will be in scope. 512 513 Returns: 514 pd.Series: The expressions of all linear constraints in the set. 515 """ 516 return _attribute_series( 517 # pylint: disable=g-long-lambda 518 func=lambda c: mbh.FlatExpr( 519 # pylint: disable=g-complex-comprehension 520 [ 521 Variable(self.__helper, var_id) 522 for var_id in c.helper.constraint_var_indices(c.index) 523 ], 524 c.helper.constraint_coefficients(c.index), 525 0.0, 526 ), 527 values=self._get_linear_constraints(constraints), 528 )
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.
530 def get_linear_constraint_lower_bounds( 531 self, constraints: Optional[_IndexOrSeries] = None 532 ) -> pd.Series: 533 """Gets the lower bounds of all linear constraints in the set. 534 535 If `constraints` is a `pd.Index`, then the output will be indexed by the 536 constraints. If `constraints` is a `pd.Series` indexed by the underlying 537 dimensions, then the output will be indexed by the same underlying 538 dimensions. 539 540 Args: 541 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 542 constraints from which to get the lower bounds. If unspecified, all 543 linear constraints will be in scope. 544 545 Returns: 546 pd.Series: The lower bounds of all linear constraints in the set. 547 """ 548 return _attribute_series( 549 func=lambda c: c.lower_bound, # pylint: disable=protected-access 550 values=self._get_linear_constraints(constraints), 551 )
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.
553 def get_linear_constraint_upper_bounds( 554 self, constraints: Optional[_IndexOrSeries] = None 555 ) -> pd.Series: 556 """Gets the upper bounds of all linear constraints in the set. 557 558 If `constraints` is a `pd.Index`, then the output will be indexed by the 559 constraints. If `constraints` is a `pd.Series` indexed by the underlying 560 dimensions, then the output will be indexed by the same underlying 561 dimensions. 562 563 Args: 564 constraints (Union[pd.Index, pd.Series]): Optional. The set of linear 565 constraints. If unspecified, all linear constraints will be in scope. 566 567 Returns: 568 pd.Series: The upper bounds of all linear constraints in the set. 569 """ 570 return _attribute_series( 571 func=lambda c: c.upper_bound, # pylint: disable=protected-access 572 values=self._get_linear_constraints(constraints), 573 )
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.
575 def get_variables(self) -> pd.Index: 576 """Gets all variables in the model.""" 577 return pd.Index( 578 [self.var_from_index(i) for i in range(self.num_variables)], 579 name="variable", 580 )
Gets all variables in the model.
582 def get_variable_lower_bounds( 583 self, variables: Optional[_IndexOrSeries] = None 584 ) -> pd.Series: 585 """Gets the lower bounds of all variables in the set. 586 587 If `variables` is a `pd.Index`, then the output will be indexed by the 588 variables. If `variables` is a `pd.Series` indexed by the underlying 589 dimensions, then the output will be indexed by the same underlying 590 dimensions. 591 592 Args: 593 variables (Union[pd.Index, pd.Series]): Optional. The set of variables 594 from which to get the lower bounds. If unspecified, all variables will 595 be in scope. 596 597 Returns: 598 pd.Series: The lower bounds of all variables in the set. 599 """ 600 return _attribute_series( 601 func=lambda v: v.lower_bound, # pylint: disable=protected-access 602 values=self._get_variables(variables), 603 )
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.
605 def get_variable_upper_bounds( 606 self, variables: Optional[_IndexOrSeries] = None 607 ) -> pd.Series: 608 """Gets the upper bounds of all variables in the set. 609 610 Args: 611 variables (Union[pd.Index, pd.Series]): Optional. The set of variables 612 from which to get the upper bounds. If unspecified, all variables will 613 be in scope. 614 615 Returns: 616 pd.Series: The upper bounds of all variables in the set. 617 """ 618 return _attribute_series( 619 func=lambda v: v.upper_bound, # pylint: disable=protected-access 620 values=self._get_variables(variables), 621 )
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.
625 def new_var( 626 self, lb: NumberT, ub: NumberT, is_integer: bool, name: Optional[str] 627 ) -> Variable: 628 """Create an integer variable with domain [lb, ub]. 629 630 Args: 631 lb: Lower bound of the variable. 632 ub: Upper bound of the variable. 633 is_integer: Indicates if the variable must take integral values. 634 name: The name of the variable. 635 636 Returns: 637 a variable whose domain is [lb, ub]. 638 """ 639 if name: 640 return Variable(self.__helper, lb, ub, is_integer, name) 641 else: 642 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].
644 def new_int_var( 645 self, lb: NumberT, ub: NumberT, name: Optional[str] = None 646 ) -> Variable: 647 """Create an integer variable with domain [lb, ub]. 648 649 Args: 650 lb: Lower bound of the variable. 651 ub: Upper bound of the variable. 652 name: The name of the variable. 653 654 Returns: 655 a variable whose domain is [lb, ub]. 656 """ 657 658 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].
660 def new_num_var( 661 self, lb: NumberT, ub: NumberT, name: Optional[str] = None 662 ) -> Variable: 663 """Create an integer variable with domain [lb, ub]. 664 665 Args: 666 lb: Lower bound of the variable. 667 ub: Upper bound of the variable. 668 name: The name of the variable. 669 670 Returns: 671 a variable whose domain is [lb, ub]. 672 """ 673 674 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].
676 def new_bool_var(self, name: Optional[str] = None) -> Variable: 677 """Creates a 0-1 variable with the given name.""" 678 return self.new_var(0, 1, True, name) # numpy-scalars
Creates a 0-1 variable with the given name.
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.
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.Seriesis 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.Seriesis 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.Seriesis 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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the
lowerboundis greater than theupperbound. - ValueError: if the index of
lower_bound,upper_bound, oris_integer - does not match the input index.
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.Seriesis 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.Seriesis 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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the
lowerboundis greater than theupperbound. - ValueError: if the index of
lower_bound,upper_bound, oris_integer - does not match the input index.
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.Seriesis 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.Seriesis 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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the
lowerboundis greater than theupperbound. - ValueError: if the index of
lower_bound,upper_bound, oris_integer - does not match the input index.
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
indexis invalid (e.g. aDataFrame). - ValueError: if the
nameis not a valid identifier or already exists. - ValueError: if the
lowerboundis greater than theupperbound. - ValueError: if the index of
lower_bound,upper_bound, oris_integer - does not match the input index.
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.
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.
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:
- ct: A
BoundedLinearExpression. - name: An optional name.
Returns:
An instance of the
Constraintclass.
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.
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.
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.
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:
- ct: A
BoundedLinearExpression. - var: The indicator variable
- value: the indicator value
- name: An optional name.
Returns:
An instance of the
Constraintclass.
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()
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.
1032 def minimize(self, linear_expr: LinearExprT) -> None: 1033 """Minimizes the given objective.""" 1034 self.__optimize(linear_expr, False)
Minimizes the given objective.
1036 def maximize(self, linear_expr: LinearExprT) -> None: 1037 """Maximizes the given objective.""" 1038 self.__optimize(linear_expr, True)
Maximizes the given objective.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
1172 @property 1173 def name(self) -> str: 1174 """The name of the model.""" 1175 return self.__helper.name()
The name of the model.
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.
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.
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.
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.
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.
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.
1228 def stop_search(self): 1229 """Stops the current search asynchronously.""" 1230 self.__solve_helper.interrupt_solve()
Stops the current search asynchronously.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.