Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
indicator_constraints.py
Go to the documentation of this file.
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
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
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}")
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)):
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(
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)