ortools.math_opt.python.indicator_constraints
Linear constraint in a model.
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"""Linear constraint in a model.""" 15 16from typing import Any, Iterator, Optional 17 18from ortools.math_opt.elemental.python import enums 19from ortools.math_opt.python import from_model 20from ortools.math_opt.python import variables 21from ortools.math_opt.python.elemental import elemental 22 23 24class IndicatorConstraint(from_model.FromModel): 25 """An indicator constraint for an optimization model. 26 27 An IndicatorConstraint adds the following restriction on feasible solutions to 28 an optimization model: 29 if z == 1 then lb <= sum_{i in I} a_i * x_i <= ub 30 where z is a binary decision variable (or its negation) and x_i are the 31 decision variables of the problem. Equality constraints lb == ub is allowed, 32 which models the constraint: 33 if z == 1 then sum_{i in I} a_i * x_i == b 34 Setting lb > ub will result in an InvalidArgument error at solve time. 35 36 Indicator constraints have limited mutability. You can delete a variable 37 that the constraint uses, or you can delete the entire constraint. You 38 currently cannot update bounds or coefficients. This may change in future 39 versions. 40 41 If the indicator variable is deleted or was None at creation time, the 42 constraint will lead to an invalid model at solve time, unless the constraint 43 is deleted before solving. 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 an IndicatorConstraint directly, use 49 Model.add_indicator_constraint() instead. Two IndicatorConstraint objects can 50 represent the same constraint (for the same model). They will have the same 51 underlying IndicatorConstraint.elemental for storing the data. The 52 IndicatorConstraint class is simply a reference to an 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_indicator_constraint() and get_indicator_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.INDICATOR_CONSTRAINT_LOWER_BOUND, (self._id,) 68 ) 69 70 @property 71 def upper_bound(self) -> float: 72 return self._elemental.get_attr( 73 enums.DoubleAttr1.INDICATOR_CONSTRAINT_UPPER_BOUND, (self._id,) 74 ) 75 76 @property 77 def activate_on_zero(self) -> bool: 78 return self._elemental.get_attr( 79 enums.BoolAttr1.INDICATOR_CONSTRAINT_ACTIVATE_ON_ZERO, (self._id,) 80 ) 81 82 @property 83 def indicator_variable(self) -> Optional[variables.Variable]: 84 var_id = self._elemental.get_attr( 85 enums.VariableAttr1.INDICATOR_CONSTRAINT_INDICATOR, (self._id,) 86 ) 87 if var_id < 0: 88 return None 89 return variables.Variable(self._elemental, var_id) 90 91 @property 92 def name(self) -> str: 93 return self._elemental.get_element_name( 94 enums.ElementType.INDICATOR_CONSTRAINT, self._id 95 ) 96 97 @property 98 def id(self) -> int: 99 return self._id 100 101 @property 102 def elemental(self) -> elemental.Elemental: 103 """Internal use only.""" 104 return self._elemental 105 106 def get_coefficient(self, var: variables.Variable) -> float: 107 from_model.model_is_same(var, self) 108 return self._elemental.get_attr( 109 enums.DoubleAttr2.INDICATOR_CONSTRAINT_LINEAR_COEFFICIENT, 110 (self._id, var.id), 111 ) 112 113 def terms(self) -> Iterator[variables.LinearTerm]: 114 """Yields the variable/coefficient pairs with nonzero coefficient for this linear constraint.""" 115 keys = self._elemental.slice_attr( 116 enums.DoubleAttr2.INDICATOR_CONSTRAINT_LINEAR_COEFFICIENT, 0, self._id 117 ) 118 coefs = self._elemental.get_attrs( 119 enums.DoubleAttr2.INDICATOR_CONSTRAINT_LINEAR_COEFFICIENT, keys 120 ) 121 for i in range(len(keys)): 122 yield variables.LinearTerm( 123 variable=variables.Variable(self._elemental, int(keys[i, 1])), 124 coefficient=float(coefs[i]), 125 ) 126 127 def get_implied_constraint(self) -> variables.BoundedLinearExpression: 128 """Returns the bounded expression from lower_bound, upper_bound and terms.""" 129 return variables.BoundedLinearExpression( 130 self.lower_bound, variables.LinearSum(self.terms()), self.upper_bound 131 ) 132 133 def __str__(self): 134 """Returns the name, or a string containing the id if the name is empty.""" 135 return self.name if self.name else f"linear_constraint_{self.id}" 136 137 def __repr__(self): 138 return f"<LinearConstraint id: {self.id}, name: {self.name!r}>" 139 140 def __eq__(self, other: Any) -> bool: 141 if isinstance(other, IndicatorConstraint): 142 return self._id == other._id and self._elemental is other._elemental 143 return False 144 145 def __hash__(self) -> int: 146 return hash(self._id)
25class IndicatorConstraint(from_model.FromModel): 26 """An indicator constraint for an optimization model. 27 28 An IndicatorConstraint adds the following restriction on feasible solutions to 29 an optimization model: 30 if z == 1 then lb <= sum_{i in I} a_i * x_i <= ub 31 where z is a binary decision variable (or its negation) and x_i are the 32 decision variables of the problem. Equality constraints lb == ub is allowed, 33 which models the constraint: 34 if z == 1 then sum_{i in I} a_i * x_i == b 35 Setting lb > ub will result in an InvalidArgument error at solve time. 36 37 Indicator constraints have limited mutability. You can delete a variable 38 that the constraint uses, or you can delete the entire constraint. You 39 currently cannot update bounds or coefficients. This may change in future 40 versions. 41 42 If the indicator variable is deleted or was None at creation time, the 43 constraint will lead to an invalid model at solve time, unless the constraint 44 is deleted before solving. 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 an IndicatorConstraint directly, use 50 Model.add_indicator_constraint() instead. Two IndicatorConstraint objects can 51 represent the same constraint (for the same model). They will have the same 52 underlying IndicatorConstraint.elemental for storing the data. The 53 IndicatorConstraint class is simply a reference to an 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_indicator_constraint() and get_indicator_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.INDICATOR_CONSTRAINT_LOWER_BOUND, (self._id,) 69 ) 70 71 @property 72 def upper_bound(self) -> float: 73 return self._elemental.get_attr( 74 enums.DoubleAttr1.INDICATOR_CONSTRAINT_UPPER_BOUND, (self._id,) 75 ) 76 77 @property 78 def activate_on_zero(self) -> bool: 79 return self._elemental.get_attr( 80 enums.BoolAttr1.INDICATOR_CONSTRAINT_ACTIVATE_ON_ZERO, (self._id,) 81 ) 82 83 @property 84 def indicator_variable(self) -> Optional[variables.Variable]: 85 var_id = self._elemental.get_attr( 86 enums.VariableAttr1.INDICATOR_CONSTRAINT_INDICATOR, (self._id,) 87 ) 88 if var_id < 0: 89 return None 90 return variables.Variable(self._elemental, var_id) 91 92 @property 93 def name(self) -> str: 94 return self._elemental.get_element_name( 95 enums.ElementType.INDICATOR_CONSTRAINT, self._id 96 ) 97 98 @property 99 def id(self) -> int: 100 return self._id 101 102 @property 103 def elemental(self) -> elemental.Elemental: 104 """Internal use only.""" 105 return self._elemental 106 107 def get_coefficient(self, var: variables.Variable) -> float: 108 from_model.model_is_same(var, self) 109 return self._elemental.get_attr( 110 enums.DoubleAttr2.INDICATOR_CONSTRAINT_LINEAR_COEFFICIENT, 111 (self._id, var.id), 112 ) 113 114 def terms(self) -> Iterator[variables.LinearTerm]: 115 """Yields the variable/coefficient pairs with nonzero coefficient for this linear constraint.""" 116 keys = self._elemental.slice_attr( 117 enums.DoubleAttr2.INDICATOR_CONSTRAINT_LINEAR_COEFFICIENT, 0, self._id 118 ) 119 coefs = self._elemental.get_attrs( 120 enums.DoubleAttr2.INDICATOR_CONSTRAINT_LINEAR_COEFFICIENT, keys 121 ) 122 for i in range(len(keys)): 123 yield variables.LinearTerm( 124 variable=variables.Variable(self._elemental, int(keys[i, 1])), 125 coefficient=float(coefs[i]), 126 ) 127 128 def get_implied_constraint(self) -> variables.BoundedLinearExpression: 129 """Returns the bounded expression from lower_bound, upper_bound and terms.""" 130 return variables.BoundedLinearExpression( 131 self.lower_bound, variables.LinearSum(self.terms()), self.upper_bound 132 ) 133 134 def __str__(self): 135 """Returns the name, or a string containing the id if the name is empty.""" 136 return self.name if self.name else f"linear_constraint_{self.id}" 137 138 def __repr__(self): 139 return f"<LinearConstraint id: {self.id}, name: {self.name!r}>" 140 141 def __eq__(self, other: Any) -> bool: 142 if isinstance(other, IndicatorConstraint): 143 return self._id == other._id and self._elemental is other._elemental 144 return False 145 146 def __hash__(self) -> int: 147 return hash(self._id)
An indicator constraint for an optimization model.
An IndicatorConstraint adds the following restriction on feasible solutions to an optimization model: if z == 1 then lb <= sum_{i in I} a_i * x_i <= ub where z is a binary decision variable (or its negation) and x_i are the decision variables of the problem. Equality constraints lb == ub is allowed, which models the constraint: if z == 1 then sum_{i in I} a_i * x_i == b Setting lb > ub will result in an InvalidArgument error at solve time.
Indicator constraints have limited mutability. You can delete a variable that the constraint uses, or you can delete the entire constraint. You currently cannot update bounds or coefficients. This may change in future versions.
If the indicator variable is deleted or was None at creation time, the constraint will lead to an invalid model at solve time, unless the constraint is deleted before solving.
The name is optional, read only, and used only for debugging. Non-empty names should be distinct.
Do not create an IndicatorConstraint directly, use Model.add_indicator_constraint() instead. Two IndicatorConstraint objects can represent the same constraint (for the same model). They will have the same underlying IndicatorConstraint.elemental for storing the data. The IndicatorConstraint 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_indicator_constraint() and get_indicator_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_indicator_constraint() and get_indicator_constraint()).
102 @property 103 def elemental(self) -> elemental.Elemental: 104 """Internal use only.""" 105 return self._elemental
Internal use only.
114 def terms(self) -> Iterator[variables.LinearTerm]: 115 """Yields the variable/coefficient pairs with nonzero coefficient for this linear constraint.""" 116 keys = self._elemental.slice_attr( 117 enums.DoubleAttr2.INDICATOR_CONSTRAINT_LINEAR_COEFFICIENT, 0, self._id 118 ) 119 coefs = self._elemental.get_attrs( 120 enums.DoubleAttr2.INDICATOR_CONSTRAINT_LINEAR_COEFFICIENT, keys 121 ) 122 for i in range(len(keys)): 123 yield variables.LinearTerm( 124 variable=variables.Variable(self._elemental, int(keys[i, 1])), 125 coefficient=float(coefs[i]), 126 )
Yields the variable/coefficient pairs with nonzero coefficient for this linear constraint.
128 def get_implied_constraint(self) -> variables.BoundedLinearExpression: 129 """Returns the bounded expression from lower_bound, upper_bound and terms.""" 130 return variables.BoundedLinearExpression( 131 self.lower_bound, variables.LinearSum(self.terms()), self.upper_bound 132 )
Returns the bounded expression from lower_bound, upper_bound and terms.