Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
model_builder.py
Go to the documentation of this file.
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
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
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
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
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:
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:
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
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."""
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."""
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."""
295 self.__helper.clear_constraint_terms(self.__index)
296
297
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
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:
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:
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
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."""
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."""
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."""
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
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
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
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
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
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
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
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
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
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
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
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)