ortools.math_opt.python.linear_constraints
Linear constraint in a model.
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"""Linear constraint in a model.""" 16 17from typing import Any, Iterator, NamedTuple 18 19from ortools.math_opt.elemental.python import enums 20from ortools.math_opt.python import from_model 21from ortools.math_opt.python import variables 22from ortools.math_opt.python.elemental import elemental 23 24 25class LinearConstraint(from_model.FromModel): 26 """A linear constraint for an optimization model. 27 28 A LinearConstraint adds the following restriction on feasible solutions to an 29 optimization model: 30 lb <= sum_{i in I} a_i * x_i <= ub 31 where x_i are the decision variables of the problem. lb == ub is allowed, this 32 models the equality constraint: 33 sum_{i in I} a_i * x_i == b 34 Setting lb > ub will result in an InvalidArgument error at solve time (the 35 values are allowed to cross temporarily between solves). 36 37 A LinearConstraint can be configured as follows: 38 * lower_bound: a float property, lb above. Should not be NaN nor +inf. 39 * upper_bound: a float property, ub above. Should not be NaN nor -inf. 40 * set_coefficient() and get_coefficient(): get and set the a_i * x_i 41 terms. The variable must be from the same model as this constraint, and 42 the a_i must be finite and not NaN. The coefficient for any variable not 43 set is 0.0, and setting a coefficient to 0.0 removes it from I above. 44 45 The name is optional, read only, and used only for debugging. Non-empty names 46 should be distinct. 47 48 Do not create a LinearConstraint directly, use Model.add_linear_constraint() 49 instead. Two LinearConstraint objects can represent the same constraint (for 50 the same model). They will have the same underlying LinearConstraint.elemental 51 for storing the data. The LinearConstraint class is simply a reference to an 52 Elemental. 53 """ 54 55 __slots__ = "_elemental", "_id" 56 57 def __init__(self, elem: elemental.Elemental, cid: int) -> None: 58 """Internal only, prefer Model functions (add_linear_constraint() and get_linear_constraint()).""" 59 if not isinstance(cid, int): 60 raise TypeError(f"cid type should be int, was:{type(cid).__name__!r}") 61 self._elemental: elemental.Elemental = elem 62 self._id: int = cid 63 64 @property 65 def lower_bound(self) -> float: 66 return self._elemental.get_attr( 67 enums.DoubleAttr1.LINEAR_CONSTRAINT_LOWER_BOUND, (self._id,) 68 ) 69 70 @lower_bound.setter 71 def lower_bound(self, value: float) -> None: 72 self._elemental.set_attr( 73 enums.DoubleAttr1.LINEAR_CONSTRAINT_LOWER_BOUND, (self._id,), value 74 ) 75 76 @property 77 def upper_bound(self) -> float: 78 return self._elemental.get_attr( 79 enums.DoubleAttr1.LINEAR_CONSTRAINT_UPPER_BOUND, (self._id,) 80 ) 81 82 @upper_bound.setter 83 def upper_bound(self, value: float) -> None: 84 self._elemental.set_attr( 85 enums.DoubleAttr1.LINEAR_CONSTRAINT_UPPER_BOUND, (self._id,), value 86 ) 87 88 @property 89 def name(self) -> str: 90 return self._elemental.get_element_name( 91 enums.ElementType.LINEAR_CONSTRAINT, self._id 92 ) 93 94 @property 95 def id(self) -> int: 96 return self._id 97 98 @property 99 def elemental(self) -> elemental.Elemental: 100 """Internal use only.""" 101 return self._elemental 102 103 def set_coefficient(self, var: variables.Variable, coefficient: float) -> None: 104 from_model.model_is_same(var, self) 105 self._elemental.set_attr( 106 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, 107 (self._id, var.id), 108 coefficient, 109 ) 110 111 def get_coefficient(self, var: variables.Variable) -> float: 112 from_model.model_is_same(var, self) 113 return self._elemental.get_attr( 114 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, (self._id, var.id) 115 ) 116 117 def terms(self) -> Iterator[variables.LinearTerm]: 118 """Yields the variable/coefficient pairs with nonzero coefficient for this linear constraint.""" 119 keys = self._elemental.slice_attr( 120 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, 0, self._id 121 ) 122 coefs = self._elemental.get_attrs( 123 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, keys 124 ) 125 for i in range(len(keys)): 126 yield variables.LinearTerm( 127 variable=variables.Variable(self._elemental, int(keys[i, 1])), 128 coefficient=float(coefs[i]), 129 ) 130 131 def as_bounded_linear_expression(self) -> variables.BoundedLinearExpression: 132 """Returns the bounded expression from lower_bound, upper_bound and terms.""" 133 return variables.BoundedLinearExpression( 134 self.lower_bound, variables.LinearSum(self.terms()), self.upper_bound 135 ) 136 137 def __str__(self): 138 """Returns the name, or a string containing the id if the name is empty.""" 139 return self.name if self.name else f"linear_constraint_{self.id}" 140 141 def __repr__(self): 142 return f"<LinearConstraint id: {self.id}, name: {self.name!r}>" 143 144 def __eq__(self, other: Any) -> bool: 145 if isinstance(other, LinearConstraint): 146 return self._id == other._id and self._elemental is other._elemental 147 return False 148 149 def __hash__(self) -> int: 150 return hash(self._id) 151 152 153class LinearConstraintMatrixEntry(NamedTuple): 154 linear_constraint: LinearConstraint 155 variable: variables.Variable 156 coefficient: float
26class LinearConstraint(from_model.FromModel): 27 """A linear constraint for an optimization model. 28 29 A LinearConstraint adds the following restriction on feasible solutions to an 30 optimization model: 31 lb <= sum_{i in I} a_i * x_i <= ub 32 where x_i are the decision variables of the problem. lb == ub is allowed, this 33 models the equality constraint: 34 sum_{i in I} a_i * x_i == b 35 Setting lb > ub will result in an InvalidArgument error at solve time (the 36 values are allowed to cross temporarily between solves). 37 38 A LinearConstraint can be configured as follows: 39 * lower_bound: a float property, lb above. Should not be NaN nor +inf. 40 * upper_bound: a float property, ub above. Should not be NaN nor -inf. 41 * set_coefficient() and get_coefficient(): get and set the a_i * x_i 42 terms. The variable must be from the same model as this constraint, and 43 the a_i must be finite and not NaN. The coefficient for any variable not 44 set is 0.0, and setting a coefficient to 0.0 removes it from I above. 45 46 The name is optional, read only, and used only for debugging. Non-empty names 47 should be distinct. 48 49 Do not create a LinearConstraint directly, use Model.add_linear_constraint() 50 instead. Two LinearConstraint objects can represent the same constraint (for 51 the same model). They will have the same underlying LinearConstraint.elemental 52 for storing the data. The LinearConstraint class is simply a reference to an 53 Elemental. 54 """ 55 56 __slots__ = "_elemental", "_id" 57 58 def __init__(self, elem: elemental.Elemental, cid: int) -> None: 59 """Internal only, prefer Model functions (add_linear_constraint() and get_linear_constraint()).""" 60 if not isinstance(cid, int): 61 raise TypeError(f"cid type should be int, was:{type(cid).__name__!r}") 62 self._elemental: elemental.Elemental = elem 63 self._id: int = cid 64 65 @property 66 def lower_bound(self) -> float: 67 return self._elemental.get_attr( 68 enums.DoubleAttr1.LINEAR_CONSTRAINT_LOWER_BOUND, (self._id,) 69 ) 70 71 @lower_bound.setter 72 def lower_bound(self, value: float) -> None: 73 self._elemental.set_attr( 74 enums.DoubleAttr1.LINEAR_CONSTRAINT_LOWER_BOUND, (self._id,), value 75 ) 76 77 @property 78 def upper_bound(self) -> float: 79 return self._elemental.get_attr( 80 enums.DoubleAttr1.LINEAR_CONSTRAINT_UPPER_BOUND, (self._id,) 81 ) 82 83 @upper_bound.setter 84 def upper_bound(self, value: float) -> None: 85 self._elemental.set_attr( 86 enums.DoubleAttr1.LINEAR_CONSTRAINT_UPPER_BOUND, (self._id,), value 87 ) 88 89 @property 90 def name(self) -> str: 91 return self._elemental.get_element_name( 92 enums.ElementType.LINEAR_CONSTRAINT, self._id 93 ) 94 95 @property 96 def id(self) -> int: 97 return self._id 98 99 @property 100 def elemental(self) -> elemental.Elemental: 101 """Internal use only.""" 102 return self._elemental 103 104 def set_coefficient(self, var: variables.Variable, coefficient: float) -> None: 105 from_model.model_is_same(var, self) 106 self._elemental.set_attr( 107 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, 108 (self._id, var.id), 109 coefficient, 110 ) 111 112 def get_coefficient(self, var: variables.Variable) -> float: 113 from_model.model_is_same(var, self) 114 return self._elemental.get_attr( 115 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, (self._id, var.id) 116 ) 117 118 def terms(self) -> Iterator[variables.LinearTerm]: 119 """Yields the variable/coefficient pairs with nonzero coefficient for this linear constraint.""" 120 keys = self._elemental.slice_attr( 121 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, 0, self._id 122 ) 123 coefs = self._elemental.get_attrs( 124 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, keys 125 ) 126 for i in range(len(keys)): 127 yield variables.LinearTerm( 128 variable=variables.Variable(self._elemental, int(keys[i, 1])), 129 coefficient=float(coefs[i]), 130 ) 131 132 def as_bounded_linear_expression(self) -> variables.BoundedLinearExpression: 133 """Returns the bounded expression from lower_bound, upper_bound and terms.""" 134 return variables.BoundedLinearExpression( 135 self.lower_bound, variables.LinearSum(self.terms()), self.upper_bound 136 ) 137 138 def __str__(self): 139 """Returns the name, or a string containing the id if the name is empty.""" 140 return self.name if self.name else f"linear_constraint_{self.id}" 141 142 def __repr__(self): 143 return f"<LinearConstraint id: {self.id}, name: {self.name!r}>" 144 145 def __eq__(self, other: Any) -> bool: 146 if isinstance(other, LinearConstraint): 147 return self._id == other._id and self._elemental is other._elemental 148 return False 149 150 def __hash__(self) -> int: 151 return hash(self._id)
A linear constraint for an optimization model.
A LinearConstraint adds the following restriction on feasible solutions to an optimization model: lb <= sum_{i in I} a_i * x_i <= ub where x_i are the decision variables of the problem. lb == ub is allowed, this models the equality constraint: sum_{i in I} a_i * x_i == b Setting lb > ub will result in an InvalidArgument error at solve time (the values are allowed to cross temporarily between solves).
A LinearConstraint can be configured as follows:
- lower_bound: a float property, lb above. Should not be NaN nor +inf.
- upper_bound: a float property, ub above. Should not be NaN nor -inf.
- set_coefficient() and get_coefficient(): get and set the a_i * x_i terms. The variable must be from the same model as this constraint, and the a_i must be finite and not NaN. The coefficient for any variable not set is 0.0, and setting a coefficient to 0.0 removes it from I above.
The name is optional, read only, and used only for debugging. Non-empty names should be distinct.
Do not create a LinearConstraint directly, use Model.add_linear_constraint() instead. Two LinearConstraint objects can represent the same constraint (for the same model). They will have the same underlying LinearConstraint.elemental for storing the data. The LinearConstraint class is simply a reference to an Elemental.
58 def __init__(self, elem: elemental.Elemental, cid: int) -> None: 59 """Internal only, prefer Model functions (add_linear_constraint() and get_linear_constraint()).""" 60 if not isinstance(cid, int): 61 raise TypeError(f"cid type should be int, was:{type(cid).__name__!r}") 62 self._elemental: elemental.Elemental = elem 63 self._id: int = cid
Internal only, prefer Model functions (add_linear_constraint() and get_linear_constraint()).
99 @property 100 def elemental(self) -> elemental.Elemental: 101 """Internal use only.""" 102 return self._elemental
Internal use only.
118 def terms(self) -> Iterator[variables.LinearTerm]: 119 """Yields the variable/coefficient pairs with nonzero coefficient for this linear constraint.""" 120 keys = self._elemental.slice_attr( 121 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, 0, self._id 122 ) 123 coefs = self._elemental.get_attrs( 124 enums.DoubleAttr2.LINEAR_CONSTRAINT_COEFFICIENT, keys 125 ) 126 for i in range(len(keys)): 127 yield variables.LinearTerm( 128 variable=variables.Variable(self._elemental, int(keys[i, 1])), 129 coefficient=float(coefs[i]), 130 )
Yields the variable/coefficient pairs with nonzero coefficient for this linear constraint.
132 def as_bounded_linear_expression(self) -> variables.BoundedLinearExpression: 133 """Returns the bounded expression from lower_bound, upper_bound and terms.""" 134 return variables.BoundedLinearExpression( 135 self.lower_bound, variables.LinearSum(self.terms()), self.upper_bound 136 )
Returns the bounded expression from lower_bound, upper_bound and terms.
154class LinearConstraintMatrixEntry(NamedTuple): 155 linear_constraint: LinearConstraint 156 variable: variables.Variable 157 coefficient: float
LinearConstraintMatrixEntry(linear_constraint, variable, coefficient)
Create new instance of LinearConstraintMatrixEntry(linear_constraint, variable, coefficient)