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