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)
class IndicatorConstraint(ortools.math_opt.python.from_model.FromModel):
 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.

IndicatorConstraint( elem: ortools.math_opt.python.elemental.elemental.Elemental, cid: int)
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()).

lower_bound: float
65    @property
66    def lower_bound(self) -> float:
67        return self._elemental.get_attr(
68            enums.DoubleAttr1.INDICATOR_CONSTRAINT_LOWER_BOUND, (self._id,)
69        )
upper_bound: float
71    @property
72    def upper_bound(self) -> float:
73        return self._elemental.get_attr(
74            enums.DoubleAttr1.INDICATOR_CONSTRAINT_UPPER_BOUND, (self._id,)
75        )
activate_on_zero: bool
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        )
indicator_variable: Optional[ortools.math_opt.python.variables.Variable]
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)
name: str
92    @property
93    def name(self) -> str:
94        return self._elemental.get_element_name(
95            enums.ElementType.INDICATOR_CONSTRAINT, self._id
96        )
id: int
 98    @property
 99    def id(self) -> int:
100        return self._id
102    @property
103    def elemental(self) -> elemental.Elemental:
104        """Internal use only."""
105        return self._elemental

Internal use only.

def get_coefficient(self, var: ortools.math_opt.python.variables.Variable) -> float:
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        )
def terms(self) -> Iterator[ortools.math_opt.python.variables.LinearTerm]:
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.

def get_implied_constraint( self) -> ortools.math_opt.python.bounded_expressions.BoundedExpression[ForwardRef('LinearBase')]:
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.