ortools.math_opt.python.normalized_inequality
Data structures for linear and quadratic constraints.
In contrast to BoundedLinearExpression and related structures, there is no offset inside the inequality.
This is not part of the MathOpt public API, do not depend on it externally.
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"""Data structures for linear and quadratic constraints. 16 17In contrast to BoundedLinearExpression and related structures, there is no 18offset inside the inequality. 19 20This is not part of the MathOpt public API, do not depend on it externally. 21""" 22 23import dataclasses 24import math 25from typing import Mapping, Optional, Union 26 27from ortools.math_opt.python import bounded_expressions 28from ortools.math_opt.python import variables 29 30_BoundedLinearExpressions = ( 31 variables.LowerBoundedLinearExpression, 32 variables.UpperBoundedLinearExpression, 33 variables.BoundedLinearExpression, 34) 35 36_BoundedQuadraticExpressions = ( 37 variables.LowerBoundedLinearExpression, 38 variables.UpperBoundedLinearExpression, 39 variables.BoundedLinearExpression, 40 variables.LowerBoundedQuadraticExpression, 41 variables.UpperBoundedQuadraticExpression, 42 variables.BoundedQuadraticExpression, 43) 44 45_BoundedExpressions = ( 46 bounded_expressions.LowerBoundedExpression, 47 bounded_expressions.UpperBoundedExpression, 48 bounded_expressions.BoundedExpression, 49) 50 51 52def _bool_error() -> TypeError: 53 return TypeError( 54 "Unsupported type for bounded_expr argument:" 55 " bool. This error can occur when trying to add != constraints " 56 "(which are not supported) or inequalities/equalities with constant " 57 "left-hand-side and right-hand-side (which are redundant or make a " 58 "model infeasible)." 59 ) 60 61 62@dataclasses.dataclass 63class NormalizedLinearInequality: 64 """Represents an inequality lb <= expr <= ub where expr's offset is zero.""" 65 66 lb: float 67 ub: float 68 coefficients: Mapping[variables.Variable, float] 69 70 def __init__( 71 self, 72 *, 73 lb: Optional[float], 74 ub: Optional[float], 75 expr: Optional[variables.LinearTypes], 76 ) -> None: 77 """Raises a ValueError if expr's offset is infinite.""" 78 lb = -math.inf if lb is None else lb 79 ub = math.inf if ub is None else ub 80 expr = 0.0 if expr is None else expr 81 if not isinstance(expr, (int, float, variables.LinearBase)): 82 raise TypeError( 83 f"Unsupported type for expr argument: {type(expr).__name__!r}." 84 ) 85 86 flat_expr = variables.as_flat_linear_expression(expr) 87 if math.isinf(flat_expr.offset): 88 raise ValueError( 89 "Trying to create a linear constraint whose expression has an" 90 " infinite offset." 91 ) 92 self.lb = lb - flat_expr.offset 93 self.ub = ub - flat_expr.offset 94 self.coefficients = flat_expr.terms 95 96 97def _normalize_bounded_linear_expression( 98 bounded_expr: variables.BoundedLinearTypes, 99) -> NormalizedLinearInequality: 100 """Converts a bounded linear expression into a NormalizedLinearInequality.""" 101 if isinstance(bounded_expr, variables.VarEqVar): 102 return NormalizedLinearInequality( 103 lb=0.0, 104 ub=0.0, 105 expr=bounded_expr.first_variable - bounded_expr.second_variable, 106 ) 107 elif isinstance(bounded_expr, _BoundedExpressions): 108 if isinstance(bounded_expr.expression, (int, float, variables.LinearBase)): 109 return NormalizedLinearInequality( 110 lb=bounded_expr.lower_bound, 111 ub=bounded_expr.upper_bound, 112 expr=bounded_expr.expression, 113 ) 114 else: 115 raise TypeError( 116 "Bad type of expression in bounded_expr:" 117 f" {type(bounded_expr.expression).__name__!r}." 118 ) 119 else: 120 raise TypeError(f"bounded_expr has bad type: {type(bounded_expr).__name__!r}.") 121 122 123# TODO(b/227214976): Update the note below and link to pytype bug number. 124# Note: bounded_expr's type includes bool only as a workaround to a pytype 125# issue. Passing a bool for bounded_expr will raise an error in runtime. 126def as_normalized_linear_inequality( 127 bounded_expr: Optional[Union[bool, variables.BoundedLinearTypes]] = None, 128 *, 129 lb: Optional[float] = None, 130 ub: Optional[float] = None, 131 expr: Optional[variables.LinearTypes] = None, 132) -> NormalizedLinearInequality: 133 """Builds a NormalizedLinearInequality. 134 135 If bounded_expr is not None, then all other arguments must be None. 136 137 If expr has a nonzero offset, it will be subtracted from both lb and ub. 138 139 When bounded_expr is unset and a named argument is unset, we use the defaults: 140 * lb: -math.inf 141 * ub: math.inf 142 * expr: 0 143 144 Args: 145 bounded_expr: a linear inequality describing the constraint. 146 lb: The lower bound when bounded_expr is None. 147 ub: The upper bound if bounded_expr is None. 148 expr: The expression when bounded_expr is None. 149 150 Returns: 151 A NormalizedLinearInequality representing the linear constraint. 152 """ 153 if isinstance(bounded_expr, bool): 154 raise _bool_error() 155 if bounded_expr is not None: 156 if lb is not None: 157 raise AssertionError( 158 "lb cannot be specified when bounded_expr is not None." 159 ) 160 if ub is not None: 161 raise AssertionError( 162 "ub cannot be specified when bounded_expr is not None." 163 ) 164 if expr is not None: 165 raise AssertionError( 166 "expr cannot be specified when bounded_expr is not None" 167 ) 168 return _normalize_bounded_linear_expression(bounded_expr) 169 # Note: NormalizedLinearInequality() will runtime check the type of expr. 170 return NormalizedLinearInequality(lb=lb, ub=ub, expr=expr) 171 172 173@dataclasses.dataclass 174class NormalizedQuadraticInequality: 175 """Represents an inequality lb <= expr <= ub where expr's offset is zero.""" 176 177 lb: float 178 ub: float 179 linear_coefficients: Mapping[variables.Variable, float] 180 quadratic_coefficients: Mapping[variables.QuadraticTermKey, float] 181 182 def __init__( 183 self, 184 *, 185 lb: Optional[float] = None, 186 ub: Optional[float] = None, 187 expr: Optional[variables.QuadraticTypes] = None, 188 ) -> None: 189 """Raises a ValueError if expr's offset is infinite.""" 190 lb = -math.inf if lb is None else lb 191 ub = math.inf if ub is None else ub 192 expr = 0.0 if expr is None else expr 193 if not isinstance( 194 expr, (int, float, variables.LinearBase, variables.QuadraticBase) 195 ): 196 raise TypeError( 197 f"Unsupported type for expr argument: {type(expr).__name__!r}." 198 ) 199 flat_expr = variables.as_flat_quadratic_expression(expr) 200 if math.isinf(flat_expr.offset): 201 raise ValueError( 202 "Trying to create a quadratic constraint whose expression has an" 203 " infinite offset." 204 ) 205 self.lb = lb - flat_expr.offset 206 self.ub = ub - flat_expr.offset 207 self.linear_coefficients = flat_expr.linear_terms 208 self.quadratic_coefficients = flat_expr.quadratic_terms 209 210 211def _normalize_bounded_quadratic_expression( 212 bounded_expr: Union[variables.BoundedQuadraticTypes, variables.BoundedLinearTypes], 213) -> NormalizedQuadraticInequality: 214 """Converts a bounded quadratic expression into a NormalizedQuadraticInequality.""" 215 if isinstance(bounded_expr, variables.VarEqVar): 216 return NormalizedQuadraticInequality( 217 lb=0.0, 218 ub=0.0, 219 expr=bounded_expr.first_variable - bounded_expr.second_variable, 220 ) 221 elif isinstance(bounded_expr, _BoundedExpressions): 222 if isinstance( 223 bounded_expr.expression, 224 (int, float, variables.LinearBase, variables.QuadraticBase), 225 ): 226 return NormalizedQuadraticInequality( 227 lb=bounded_expr.lower_bound, 228 ub=bounded_expr.upper_bound, 229 expr=bounded_expr.expression, 230 ) 231 else: 232 raise TypeError( 233 "bounded_expr.expression has bad type:" 234 f" {type(bounded_expr.expression).__name__!r}." 235 ) 236 else: 237 raise TypeError(f"bounded_expr has bad type: {type(bounded_expr).__name__!r}.") 238 239 240# TODO(b/227214976): Update the note below and link to pytype bug number. 241# Note: bounded_expr's type includes bool only as a workaround to a pytype 242# issue. Passing a bool for bounded_expr will raise an error in runtime. 243def as_normalized_quadratic_inequality( 244 bounded_expr: Optional[ 245 Union[bool, variables.BoundedLinearTypes, variables.BoundedQuadraticTypes] 246 ] = None, 247 *, 248 lb: Optional[float] = None, 249 ub: Optional[float] = None, 250 expr: Optional[variables.QuadraticTypes] = None, 251) -> NormalizedQuadraticInequality: 252 """Builds a NormalizedLinearInequality. 253 254 If bounded_expr is not None, then all other arguments must be None. 255 256 If expr has a nonzero offset, it will be subtracted from both lb and ub. 257 258 When bounded_expr is unset and a named argument is unset, we use the defaults: 259 * lb: -math.inf 260 * ub: math.inf 261 * expr: 0 262 263 Args: 264 bounded_expr: a quadratic inequality describing the constraint. 265 lb: The lower bound when bounded_expr is None. 266 ub: The upper bound if bounded_expr is None. 267 expr: The expression when bounded_expr is None. 268 269 Returns: 270 A NormalizedLinearInequality representing the linear constraint. 271 """ 272 if isinstance(bounded_expr, bool): 273 raise _bool_error() 274 if bounded_expr is not None: 275 if lb is not None: 276 raise AssertionError( 277 "lb cannot be specified when bounded_expr is not None." 278 ) 279 if ub is not None: 280 raise AssertionError( 281 "ub cannot be specified when bounded_expr is not None." 282 ) 283 if expr is not None: 284 raise AssertionError( 285 "expr cannot be specified when bounded_expr is not None" 286 ) 287 return _normalize_bounded_quadratic_expression(bounded_expr) 288 return NormalizedQuadraticInequality(lb=lb, ub=ub, expr=expr)
63@dataclasses.dataclass 64class NormalizedLinearInequality: 65 """Represents an inequality lb <= expr <= ub where expr's offset is zero.""" 66 67 lb: float 68 ub: float 69 coefficients: Mapping[variables.Variable, float] 70 71 def __init__( 72 self, 73 *, 74 lb: Optional[float], 75 ub: Optional[float], 76 expr: Optional[variables.LinearTypes], 77 ) -> None: 78 """Raises a ValueError if expr's offset is infinite.""" 79 lb = -math.inf if lb is None else lb 80 ub = math.inf if ub is None else ub 81 expr = 0.0 if expr is None else expr 82 if not isinstance(expr, (int, float, variables.LinearBase)): 83 raise TypeError( 84 f"Unsupported type for expr argument: {type(expr).__name__!r}." 85 ) 86 87 flat_expr = variables.as_flat_linear_expression(expr) 88 if math.isinf(flat_expr.offset): 89 raise ValueError( 90 "Trying to create a linear constraint whose expression has an" 91 " infinite offset." 92 ) 93 self.lb = lb - flat_expr.offset 94 self.ub = ub - flat_expr.offset 95 self.coefficients = flat_expr.terms
Represents an inequality lb <= expr <= ub where expr's offset is zero.
71 def __init__( 72 self, 73 *, 74 lb: Optional[float], 75 ub: Optional[float], 76 expr: Optional[variables.LinearTypes], 77 ) -> None: 78 """Raises a ValueError if expr's offset is infinite.""" 79 lb = -math.inf if lb is None else lb 80 ub = math.inf if ub is None else ub 81 expr = 0.0 if expr is None else expr 82 if not isinstance(expr, (int, float, variables.LinearBase)): 83 raise TypeError( 84 f"Unsupported type for expr argument: {type(expr).__name__!r}." 85 ) 86 87 flat_expr = variables.as_flat_linear_expression(expr) 88 if math.isinf(flat_expr.offset): 89 raise ValueError( 90 "Trying to create a linear constraint whose expression has an" 91 " infinite offset." 92 ) 93 self.lb = lb - flat_expr.offset 94 self.ub = ub - flat_expr.offset 95 self.coefficients = flat_expr.terms
Raises a ValueError if expr's offset is infinite.
127def as_normalized_linear_inequality( 128 bounded_expr: Optional[Union[bool, variables.BoundedLinearTypes]] = None, 129 *, 130 lb: Optional[float] = None, 131 ub: Optional[float] = None, 132 expr: Optional[variables.LinearTypes] = None, 133) -> NormalizedLinearInequality: 134 """Builds a NormalizedLinearInequality. 135 136 If bounded_expr is not None, then all other arguments must be None. 137 138 If expr has a nonzero offset, it will be subtracted from both lb and ub. 139 140 When bounded_expr is unset and a named argument is unset, we use the defaults: 141 * lb: -math.inf 142 * ub: math.inf 143 * expr: 0 144 145 Args: 146 bounded_expr: a linear inequality describing the constraint. 147 lb: The lower bound when bounded_expr is None. 148 ub: The upper bound if bounded_expr is None. 149 expr: The expression when bounded_expr is None. 150 151 Returns: 152 A NormalizedLinearInequality representing the linear constraint. 153 """ 154 if isinstance(bounded_expr, bool): 155 raise _bool_error() 156 if bounded_expr is not None: 157 if lb is not None: 158 raise AssertionError( 159 "lb cannot be specified when bounded_expr is not None." 160 ) 161 if ub is not None: 162 raise AssertionError( 163 "ub cannot be specified when bounded_expr is not None." 164 ) 165 if expr is not None: 166 raise AssertionError( 167 "expr cannot be specified when bounded_expr is not None" 168 ) 169 return _normalize_bounded_linear_expression(bounded_expr) 170 # Note: NormalizedLinearInequality() will runtime check the type of expr. 171 return NormalizedLinearInequality(lb=lb, ub=ub, expr=expr)
Builds a NormalizedLinearInequality.
If bounded_expr is not None, then all other arguments must be None.
If expr has a nonzero offset, it will be subtracted from both lb and ub.
When bounded_expr is unset and a named argument is unset, we use the defaults:
- lb: -math.inf
- ub: math.inf
- expr: 0
Arguments:
- bounded_expr: a linear inequality describing the constraint.
- lb: The lower bound when bounded_expr is None.
- ub: The upper bound if bounded_expr is None.
- expr: The expression when bounded_expr is None.
Returns:
A NormalizedLinearInequality representing the linear constraint.
174@dataclasses.dataclass 175class NormalizedQuadraticInequality: 176 """Represents an inequality lb <= expr <= ub where expr's offset is zero.""" 177 178 lb: float 179 ub: float 180 linear_coefficients: Mapping[variables.Variable, float] 181 quadratic_coefficients: Mapping[variables.QuadraticTermKey, float] 182 183 def __init__( 184 self, 185 *, 186 lb: Optional[float] = None, 187 ub: Optional[float] = None, 188 expr: Optional[variables.QuadraticTypes] = None, 189 ) -> None: 190 """Raises a ValueError if expr's offset is infinite.""" 191 lb = -math.inf if lb is None else lb 192 ub = math.inf if ub is None else ub 193 expr = 0.0 if expr is None else expr 194 if not isinstance( 195 expr, (int, float, variables.LinearBase, variables.QuadraticBase) 196 ): 197 raise TypeError( 198 f"Unsupported type for expr argument: {type(expr).__name__!r}." 199 ) 200 flat_expr = variables.as_flat_quadratic_expression(expr) 201 if math.isinf(flat_expr.offset): 202 raise ValueError( 203 "Trying to create a quadratic constraint whose expression has an" 204 " infinite offset." 205 ) 206 self.lb = lb - flat_expr.offset 207 self.ub = ub - flat_expr.offset 208 self.linear_coefficients = flat_expr.linear_terms 209 self.quadratic_coefficients = flat_expr.quadratic_terms
Represents an inequality lb <= expr <= ub where expr's offset is zero.
183 def __init__( 184 self, 185 *, 186 lb: Optional[float] = None, 187 ub: Optional[float] = None, 188 expr: Optional[variables.QuadraticTypes] = None, 189 ) -> None: 190 """Raises a ValueError if expr's offset is infinite.""" 191 lb = -math.inf if lb is None else lb 192 ub = math.inf if ub is None else ub 193 expr = 0.0 if expr is None else expr 194 if not isinstance( 195 expr, (int, float, variables.LinearBase, variables.QuadraticBase) 196 ): 197 raise TypeError( 198 f"Unsupported type for expr argument: {type(expr).__name__!r}." 199 ) 200 flat_expr = variables.as_flat_quadratic_expression(expr) 201 if math.isinf(flat_expr.offset): 202 raise ValueError( 203 "Trying to create a quadratic constraint whose expression has an" 204 " infinite offset." 205 ) 206 self.lb = lb - flat_expr.offset 207 self.ub = ub - flat_expr.offset 208 self.linear_coefficients = flat_expr.linear_terms 209 self.quadratic_coefficients = flat_expr.quadratic_terms
Raises a ValueError if expr's offset is infinite.
244def as_normalized_quadratic_inequality( 245 bounded_expr: Optional[ 246 Union[bool, variables.BoundedLinearTypes, variables.BoundedQuadraticTypes] 247 ] = None, 248 *, 249 lb: Optional[float] = None, 250 ub: Optional[float] = None, 251 expr: Optional[variables.QuadraticTypes] = None, 252) -> NormalizedQuadraticInequality: 253 """Builds a NormalizedLinearInequality. 254 255 If bounded_expr is not None, then all other arguments must be None. 256 257 If expr has a nonzero offset, it will be subtracted from both lb and ub. 258 259 When bounded_expr is unset and a named argument is unset, we use the defaults: 260 * lb: -math.inf 261 * ub: math.inf 262 * expr: 0 263 264 Args: 265 bounded_expr: a quadratic inequality describing the constraint. 266 lb: The lower bound when bounded_expr is None. 267 ub: The upper bound if bounded_expr is None. 268 expr: The expression when bounded_expr is None. 269 270 Returns: 271 A NormalizedLinearInequality representing the linear constraint. 272 """ 273 if isinstance(bounded_expr, bool): 274 raise _bool_error() 275 if bounded_expr is not None: 276 if lb is not None: 277 raise AssertionError( 278 "lb cannot be specified when bounded_expr is not None." 279 ) 280 if ub is not None: 281 raise AssertionError( 282 "ub cannot be specified when bounded_expr is not None." 283 ) 284 if expr is not None: 285 raise AssertionError( 286 "expr cannot be specified when bounded_expr is not None" 287 ) 288 return _normalize_bounded_quadratic_expression(bounded_expr) 289 return NormalizedQuadraticInequality(lb=lb, ub=ub, expr=expr)
Builds a NormalizedLinearInequality.
If bounded_expr is not None, then all other arguments must be None.
If expr has a nonzero offset, it will be subtracted from both lb and ub.
When bounded_expr is unset and a named argument is unset, we use the defaults:
- lb: -math.inf
- ub: math.inf
- expr: 0
Arguments:
- bounded_expr: a quadratic inequality describing the constraint.
- lb: The lower bound when bounded_expr is None.
- ub: The upper bound if bounded_expr is None.
- expr: The expression when bounded_expr is None.
Returns:
A NormalizedLinearInequality representing the linear constraint.