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