ortools.math_opt.python.bounded_expressions
Bounded (above and below), upper bounded, and lower bounded expressions.
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"""Bounded (above and below), upper bounded, and lower bounded expressions.""" 15 16import math 17from typing import Any, Generic, NoReturn, Optional, Type, TypeVar 18 19_CHAINED_COMPARISON_MESSAGE = ( 20 "If you were trying to create a two-sided or " 21 "ranged linear inequality of the form `lb <= " 22 "expr <= ub`, try `(lb <= expr) <= ub` instead" 23) 24 25 26def _raise_binary_operator_type_error( 27 operator: str, 28 lhs: Type[Any], 29 rhs: Type[Any], 30 extra_message: Optional[str] = None, 31) -> NoReturn: 32 """Raises TypeError on unsupported operators.""" 33 message = ( 34 f"unsupported operand type(s) for {operator}: {lhs.__name__!r} and" 35 f" {rhs.__name__!r}" 36 ) 37 if extra_message is not None: 38 message += "\n" + extra_message 39 raise TypeError(message) 40 41 42T = TypeVar("T") 43 44 45class BoundedExpression(Generic[T]): 46 """An inequality of the form lower_bound <= expression <= upper_bound. 47 48 Where: 49 * expression is a T, typically LinearBase or QuadraticBase. 50 * lower_bound is a float. 51 * upper_bound is a float. 52 53 Note: Because of limitations related to Python's handling of chained 54 comparisons, bounded expressions cannot be directly created usign 55 overloaded comparisons as in `lower_bound <= expression <= upper_bound`. 56 One solution is to wrap one of the inequalities in parenthesis as in 57 `(lower_bound <= expression) <= upper_bound`. 58 """ 59 60 __slots__ = "_expression", "_lower_bound", "_upper_bound" 61 62 def __init__(self, lower_bound: float, expression: T, upper_bound: float) -> None: 63 self._expression: T = expression 64 self._lower_bound: float = lower_bound 65 self._upper_bound: float = upper_bound 66 67 @property 68 def expression(self) -> T: 69 return self._expression 70 71 @property 72 def lower_bound(self) -> float: 73 return self._lower_bound 74 75 @property 76 def upper_bound(self) -> float: 77 return self._upper_bound 78 79 def __bool__(self) -> bool: 80 raise TypeError( 81 "__bool__ is unsupported for BoundedExpression" 82 + "\n" 83 + _CHAINED_COMPARISON_MESSAGE 84 ) 85 86 def __str__(self): 87 return f"{self._lower_bound} <= {self._expression!s} <= {self._upper_bound}" 88 89 def __repr__(self): 90 return f"{self._lower_bound} <= {self._expression!r} <= {self._upper_bound}" 91 92 93class UpperBoundedExpression(Generic[T]): 94 """An inequality of the form expression <= upper_bound. 95 96 Where: 97 * expression is a T, and 98 * upper_bound is a float 99 """ 100 101 __slots__ = "_expression", "_upper_bound" 102 103 def __init__(self, expression: T, upper_bound: float) -> None: 104 """Operator overloading can be used instead: e.g. `x + y <= 2.0`.""" 105 self._expression: T = expression 106 self._upper_bound: float = upper_bound 107 108 @property 109 def expression(self) -> T: 110 return self._expression 111 112 @property 113 def lower_bound(self) -> float: 114 return -math.inf 115 116 @property 117 def upper_bound(self) -> float: 118 return self._upper_bound 119 120 def __ge__(self, lhs: float) -> BoundedExpression[T]: 121 if isinstance(lhs, (int, float)): 122 return BoundedExpression[T](lhs, self.expression, self.upper_bound) 123 _raise_binary_operator_type_error(">=", type(self), type(lhs)) 124 125 def __bool__(self) -> bool: 126 raise TypeError( 127 "__bool__ is unsupported for UpperBoundedExpression" 128 + "\n" 129 + _CHAINED_COMPARISON_MESSAGE 130 ) 131 132 def __str__(self): 133 return f"{self._expression!s} <= {self._upper_bound}" 134 135 def __repr__(self): 136 return f"{self._expression!r} <= {self._upper_bound}" 137 138 139class LowerBoundedExpression(Generic[T]): 140 """An inequality of the form expression >= lower_bound. 141 142 Where: 143 * expression is a linear expression, and 144 * lower_bound is a float 145 """ 146 147 __slots__ = "_expression", "_lower_bound" 148 149 def __init__(self, expression: T, lower_bound: float) -> None: 150 """Operator overloading can be used instead: e.g. `x + y >= 2.0`.""" 151 self._expression: T = expression 152 self._lower_bound: float = lower_bound 153 154 @property 155 def expression(self) -> T: 156 return self._expression 157 158 @property 159 def lower_bound(self) -> float: 160 return self._lower_bound 161 162 @property 163 def upper_bound(self) -> float: 164 return math.inf 165 166 def __le__(self, rhs: float) -> BoundedExpression[T]: 167 if isinstance(rhs, (int, float)): 168 return BoundedExpression[T](self.lower_bound, self.expression, rhs) 169 _raise_binary_operator_type_error("<=", type(self), type(rhs)) 170 171 def __bool__(self) -> bool: 172 raise TypeError( 173 "__bool__ is unsupported for LowerBoundedExpression" 174 + "\n" 175 + _CHAINED_COMPARISON_MESSAGE 176 ) 177 178 def __str__(self): 179 return f"{self._expression!s} >= {self._lower_bound}" 180 181 def __repr__(self): 182 return f"{self._expression!r} >= {self._lower_bound}"
class
BoundedExpression(typing.Generic[~T]):
46class BoundedExpression(Generic[T]): 47 """An inequality of the form lower_bound <= expression <= upper_bound. 48 49 Where: 50 * expression is a T, typically LinearBase or QuadraticBase. 51 * lower_bound is a float. 52 * upper_bound is a float. 53 54 Note: Because of limitations related to Python's handling of chained 55 comparisons, bounded expressions cannot be directly created usign 56 overloaded comparisons as in `lower_bound <= expression <= upper_bound`. 57 One solution is to wrap one of the inequalities in parenthesis as in 58 `(lower_bound <= expression) <= upper_bound`. 59 """ 60 61 __slots__ = "_expression", "_lower_bound", "_upper_bound" 62 63 def __init__(self, lower_bound: float, expression: T, upper_bound: float) -> None: 64 self._expression: T = expression 65 self._lower_bound: float = lower_bound 66 self._upper_bound: float = upper_bound 67 68 @property 69 def expression(self) -> T: 70 return self._expression 71 72 @property 73 def lower_bound(self) -> float: 74 return self._lower_bound 75 76 @property 77 def upper_bound(self) -> float: 78 return self._upper_bound 79 80 def __bool__(self) -> bool: 81 raise TypeError( 82 "__bool__ is unsupported for BoundedExpression" 83 + "\n" 84 + _CHAINED_COMPARISON_MESSAGE 85 ) 86 87 def __str__(self): 88 return f"{self._lower_bound} <= {self._expression!s} <= {self._upper_bound}" 89 90 def __repr__(self): 91 return f"{self._lower_bound} <= {self._expression!r} <= {self._upper_bound}"
An inequality of the form lower_bound <= expression <= upper_bound.
Where:
- expression is a T, typically LinearBase or QuadraticBase.
- lower_bound is a float.
- upper_bound is a float.
Note: Because of limitations related to Python's handling of chained
comparisons, bounded expressions cannot be directly created usign
overloaded comparisons as in lower_bound <= expression <= upper_bound
.
One solution is to wrap one of the inequalities in parenthesis as in
(lower_bound <= expression) <= upper_bound
.
class
UpperBoundedExpression(typing.Generic[~T]):
94class UpperBoundedExpression(Generic[T]): 95 """An inequality of the form expression <= upper_bound. 96 97 Where: 98 * expression is a T, and 99 * upper_bound is a float 100 """ 101 102 __slots__ = "_expression", "_upper_bound" 103 104 def __init__(self, expression: T, upper_bound: float) -> None: 105 """Operator overloading can be used instead: e.g. `x + y <= 2.0`.""" 106 self._expression: T = expression 107 self._upper_bound: float = upper_bound 108 109 @property 110 def expression(self) -> T: 111 return self._expression 112 113 @property 114 def lower_bound(self) -> float: 115 return -math.inf 116 117 @property 118 def upper_bound(self) -> float: 119 return self._upper_bound 120 121 def __ge__(self, lhs: float) -> BoundedExpression[T]: 122 if isinstance(lhs, (int, float)): 123 return BoundedExpression[T](lhs, self.expression, self.upper_bound) 124 _raise_binary_operator_type_error(">=", type(self), type(lhs)) 125 126 def __bool__(self) -> bool: 127 raise TypeError( 128 "__bool__ is unsupported for UpperBoundedExpression" 129 + "\n" 130 + _CHAINED_COMPARISON_MESSAGE 131 ) 132 133 def __str__(self): 134 return f"{self._expression!s} <= {self._upper_bound}" 135 136 def __repr__(self): 137 return f"{self._expression!r} <= {self._upper_bound}"
An inequality of the form expression <= upper_bound.
Where:
- expression is a T, and
- upper_bound is a float
UpperBoundedExpression(expression: ~T, upper_bound: float)
104 def __init__(self, expression: T, upper_bound: float) -> None: 105 """Operator overloading can be used instead: e.g. `x + y <= 2.0`.""" 106 self._expression: T = expression 107 self._upper_bound: float = upper_bound
Operator overloading can be used instead: e.g. x + y <= 2.0
.
class
LowerBoundedExpression(typing.Generic[~T]):
140class LowerBoundedExpression(Generic[T]): 141 """An inequality of the form expression >= lower_bound. 142 143 Where: 144 * expression is a linear expression, and 145 * lower_bound is a float 146 """ 147 148 __slots__ = "_expression", "_lower_bound" 149 150 def __init__(self, expression: T, lower_bound: float) -> None: 151 """Operator overloading can be used instead: e.g. `x + y >= 2.0`.""" 152 self._expression: T = expression 153 self._lower_bound: float = lower_bound 154 155 @property 156 def expression(self) -> T: 157 return self._expression 158 159 @property 160 def lower_bound(self) -> float: 161 return self._lower_bound 162 163 @property 164 def upper_bound(self) -> float: 165 return math.inf 166 167 def __le__(self, rhs: float) -> BoundedExpression[T]: 168 if isinstance(rhs, (int, float)): 169 return BoundedExpression[T](self.lower_bound, self.expression, rhs) 170 _raise_binary_operator_type_error("<=", type(self), type(rhs)) 171 172 def __bool__(self) -> bool: 173 raise TypeError( 174 "__bool__ is unsupported for LowerBoundedExpression" 175 + "\n" 176 + _CHAINED_COMPARISON_MESSAGE 177 ) 178 179 def __str__(self): 180 return f"{self._expression!s} >= {self._lower_bound}" 181 182 def __repr__(self): 183 return f"{self._expression!r} >= {self._lower_bound}"
An inequality of the form expression >= lower_bound.
Where:
- expression is a linear expression, and
- lower_bound is a float
LowerBoundedExpression(expression: ~T, lower_bound: float)
150 def __init__(self, expression: T, lower_bound: float) -> None: 151 """Operator overloading can be used instead: e.g. `x + y >= 2.0`.""" 152 self._expression: T = expression 153 self._lower_bound: float = lower_bound
Operator overloading can be used instead: e.g. x + y >= 2.0
.