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