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

IndicatorConstraint()
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()).

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

Internal use only.

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

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