Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
indicator_constraints.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"""Linear constraint in a model."""
15
16from typing import Any, Iterator, Optional
17
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
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}")
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)):
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(
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)