Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
bounded_expressions.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"""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
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}"
None __init__(self, float lower_bound, T expression, float upper_bound)
NoReturn _raise_binary_operator_type_error(str operator, Type[Any] lhs, Type[Any] rhs, Optional[str] extra_message=None)