ortools.math_opt.python.compute_infeasible_subsystem_result

Data types for the result of calling `mathopt.compute_infeasible_subsystem.

  1# Copyright 2010-2024 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"""Data types for the result of calling `mathopt.compute_infeasible_subsystem."""
 15
 16import dataclasses
 17from typing import FrozenSet, Mapping
 18
 19import immutabledict
 20
 21from ortools.math_opt import infeasible_subsystem_pb2
 22from ortools.math_opt.python import model
 23from ortools.math_opt.python import result
 24
 25
 26@dataclasses.dataclass(frozen=True)
 27class ModelSubsetBounds:
 28    """Presence of the upper and lower bounds in a two-sided constraint.
 29
 30    E.g. for 1 <= x <= 2, `lower` is the constraint 1 <= x and `upper` is the
 31    constraint x <= 2.
 32
 33    Attributes:
 34      lower: If the lower bound half of the two-sided constraint is selected.
 35      upper: If the upper bound half of the two-sided constraint is selected.
 36    """
 37
 38    lower: bool = False
 39    upper: bool = False
 40
 41    def empty(self) -> bool:
 42        """Is empty if both `lower` and `upper` are False."""
 43        return not (self.lower or self.upper)
 44
 45    def to_proto(self) -> infeasible_subsystem_pb2.ModelSubsetProto.Bounds:
 46        """Returns an equivalent proto message for these bounds."""
 47        return infeasible_subsystem_pb2.ModelSubsetProto.Bounds(
 48            lower=self.lower, upper=self.upper
 49        )
 50
 51
 52def parse_model_subset_bounds(
 53    bounds: infeasible_subsystem_pb2.ModelSubsetProto.Bounds,
 54) -> ModelSubsetBounds:
 55    """Returns an equivalent `ModelSubsetBounds` to the input proto."""
 56    return ModelSubsetBounds(lower=bounds.lower, upper=bounds.upper)
 57
 58
 59@dataclasses.dataclass(frozen=True)
 60class ModelSubset:
 61    """A subset of a Model's constraints (including variable bounds/integrality).
 62
 63    When returned from `solve.compute_infeasible_subsystem`, the contained
 64    `ModelSubsetBounds` will all be nonempty.
 65
 66    Attributes:
 67      variable_bounds: The upper and/or lower bound constraints on these variables
 68        are included in the subset.
 69      variable_integrality: The constraint that a variable is integer is included
 70        in the subset.
 71      linear_constraints: The upper and/or lower bounds from these linear
 72        constraints are included in the subset.
 73    """
 74
 75    variable_bounds: Mapping[model.Variable, ModelSubsetBounds] = (
 76        immutabledict.immutabledict()
 77    )
 78    variable_integrality: FrozenSet[model.Variable] = frozenset()
 79    linear_constraints: Mapping[model.LinearConstraint, ModelSubsetBounds] = (
 80        immutabledict.immutabledict()
 81    )
 82
 83    def empty(self) -> bool:
 84        """Returns true if all the nested constraint collections are empty.
 85
 86        Warning: When `self.variable_bounds` or `self.linear_constraints` contain
 87        only ModelSubsetBounds which are themselves empty, this function will return
 88        False.
 89
 90        Returns:
 91          True if this is empty.
 92        """
 93        return not (
 94            self.variable_bounds or self.variable_integrality or self.linear_constraints
 95        )
 96
 97    def to_proto(self) -> infeasible_subsystem_pb2.ModelSubsetProto:
 98        """Returns an equivalent proto message for this `ModelSubset`."""
 99        return infeasible_subsystem_pb2.ModelSubsetProto(
100            variable_bounds={
101                var.id: bounds.to_proto()
102                for (var, bounds) in self.variable_bounds.items()
103            },
104            variable_integrality=sorted(var.id for var in self.variable_integrality),
105            linear_constraints={
106                con.id: bounds.to_proto()
107                for (con, bounds) in self.linear_constraints.items()
108            },
109        )
110
111
112def parse_model_subset(
113    model_subset: infeasible_subsystem_pb2.ModelSubsetProto, mod: model.Model
114) -> ModelSubset:
115    """Returns an equivalent `ModelSubset` to the input proto."""
116    if model_subset.quadratic_constraints:
117        raise NotImplementedError(
118            "quadratic_constraints not yet implemented for ModelSubset in Python"
119        )
120    if model_subset.second_order_cone_constraints:
121        raise NotImplementedError(
122            "second_order_cone_constraints not yet implemented for ModelSubset in"
123            " Python"
124        )
125    if model_subset.sos1_constraints:
126        raise NotImplementedError(
127            "sos1_constraints not yet implemented for ModelSubset in Python"
128        )
129    if model_subset.sos2_constraints:
130        raise NotImplementedError(
131            "sos2_constraints not yet implemented for ModelSubset in Python"
132        )
133    if model_subset.indicator_constraints:
134        raise NotImplementedError(
135            "indicator_constraints not yet implemented for ModelSubset in Python"
136        )
137    return ModelSubset(
138        variable_bounds={
139            mod.get_variable(var_id): parse_model_subset_bounds(bounds)
140            for var_id, bounds in model_subset.variable_bounds.items()
141        },
142        variable_integrality=frozenset(
143            mod.get_variable(var_id) for var_id in model_subset.variable_integrality
144        ),
145        linear_constraints={
146            mod.get_linear_constraint(con_id): parse_model_subset_bounds(bounds)
147            for con_id, bounds in model_subset.linear_constraints.items()
148        },
149    )
150
151
152@dataclasses.dataclass(frozen=True)
153class ComputeInfeasibleSubsystemResult:
154    """The result of searching for an infeasible subsystem.
155
156    This is the result of calling `mathopt.compute_infeasible_subsystem()`.
157
158    Attributes:
159      feasibility: If the problem was proven feasible, infeasible, or no
160        conclusion was reached. The fields below are ignored unless the problem
161        was proven infeasible.
162      infeasible_subsystem: Ignored unless `feasibility` is `INFEASIBLE`, a subset
163        of the model that is still infeasible.
164      is_minimal: Ignored unless `feasibility` is `INFEASIBLE`. If True, then the
165        removal of any constraint from `infeasible_subsystem` makes the sub-model
166        feasible. Note that, due to problem transformations MathOpt applies or
167        idiosyncrasies of the solvers contract, the returned infeasible subsystem
168        may not actually be minimal.
169    """
170
171    feasibility: result.FeasibilityStatus = result.FeasibilityStatus.UNDETERMINED
172    infeasible_subsystem: ModelSubset = ModelSubset()
173    is_minimal: bool = False
174
175    def to_proto(
176        self,
177    ) -> infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto:
178        """Returns an equivalent proto for this `ComputeInfeasibleSubsystemResult`."""
179        return infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto(
180            feasibility=self.feasibility.value,
181            infeasible_subsystem=self.infeasible_subsystem.to_proto(),
182            is_minimal=self.is_minimal,
183        )
184
185
186def parse_compute_infeasible_subsystem_result(
187    infeasible_system_result: infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto,
188    mod: model.Model,
189) -> ComputeInfeasibleSubsystemResult:
190    """Returns an equivalent `ComputeInfeasibleSubsystemResult` to the input proto."""
191    return ComputeInfeasibleSubsystemResult(
192        feasibility=result.FeasibilityStatus(infeasible_system_result.feasibility),
193        infeasible_subsystem=parse_model_subset(
194            infeasible_system_result.infeasible_subsystem, mod
195        ),
196        is_minimal=infeasible_system_result.is_minimal,
197    )
@dataclasses.dataclass(frozen=True)
class ModelSubsetBounds:
27@dataclasses.dataclass(frozen=True)
28class ModelSubsetBounds:
29    """Presence of the upper and lower bounds in a two-sided constraint.
30
31    E.g. for 1 <= x <= 2, `lower` is the constraint 1 <= x and `upper` is the
32    constraint x <= 2.
33
34    Attributes:
35      lower: If the lower bound half of the two-sided constraint is selected.
36      upper: If the upper bound half of the two-sided constraint is selected.
37    """
38
39    lower: bool = False
40    upper: bool = False
41
42    def empty(self) -> bool:
43        """Is empty if both `lower` and `upper` are False."""
44        return not (self.lower or self.upper)
45
46    def to_proto(self) -> infeasible_subsystem_pb2.ModelSubsetProto.Bounds:
47        """Returns an equivalent proto message for these bounds."""
48        return infeasible_subsystem_pb2.ModelSubsetProto.Bounds(
49            lower=self.lower, upper=self.upper
50        )

Presence of the upper and lower bounds in a two-sided constraint.

E.g. for 1 <= x <= 2, lower is the constraint 1 <= x and upper is the constraint x <= 2.

Attributes:
  • lower: If the lower bound half of the two-sided constraint is selected.
  • upper: If the upper bound half of the two-sided constraint is selected.
ModelSubsetBounds(lower: bool = False, upper: bool = False)
lower: bool = False
upper: bool = False
def empty(self) -> bool:
42    def empty(self) -> bool:
43        """Is empty if both `lower` and `upper` are False."""
44        return not (self.lower or self.upper)

Is empty if both lower and upper are False.

def to_proto(self) -> ortools.math_opt.infeasible_subsystem_pb2.Bounds:
46    def to_proto(self) -> infeasible_subsystem_pb2.ModelSubsetProto.Bounds:
47        """Returns an equivalent proto message for these bounds."""
48        return infeasible_subsystem_pb2.ModelSubsetProto.Bounds(
49            lower=self.lower, upper=self.upper
50        )

Returns an equivalent proto message for these bounds.

def parse_model_subset_bounds( bounds: ortools.math_opt.infeasible_subsystem_pb2.Bounds) -> ModelSubsetBounds:
53def parse_model_subset_bounds(
54    bounds: infeasible_subsystem_pb2.ModelSubsetProto.Bounds,
55) -> ModelSubsetBounds:
56    """Returns an equivalent `ModelSubsetBounds` to the input proto."""
57    return ModelSubsetBounds(lower=bounds.lower, upper=bounds.upper)

Returns an equivalent ModelSubsetBounds to the input proto.

@dataclasses.dataclass(frozen=True)
class ModelSubset:
 60@dataclasses.dataclass(frozen=True)
 61class ModelSubset:
 62    """A subset of a Model's constraints (including variable bounds/integrality).
 63
 64    When returned from `solve.compute_infeasible_subsystem`, the contained
 65    `ModelSubsetBounds` will all be nonempty.
 66
 67    Attributes:
 68      variable_bounds: The upper and/or lower bound constraints on these variables
 69        are included in the subset.
 70      variable_integrality: The constraint that a variable is integer is included
 71        in the subset.
 72      linear_constraints: The upper and/or lower bounds from these linear
 73        constraints are included in the subset.
 74    """
 75
 76    variable_bounds: Mapping[model.Variable, ModelSubsetBounds] = (
 77        immutabledict.immutabledict()
 78    )
 79    variable_integrality: FrozenSet[model.Variable] = frozenset()
 80    linear_constraints: Mapping[model.LinearConstraint, ModelSubsetBounds] = (
 81        immutabledict.immutabledict()
 82    )
 83
 84    def empty(self) -> bool:
 85        """Returns true if all the nested constraint collections are empty.
 86
 87        Warning: When `self.variable_bounds` or `self.linear_constraints` contain
 88        only ModelSubsetBounds which are themselves empty, this function will return
 89        False.
 90
 91        Returns:
 92          True if this is empty.
 93        """
 94        return not (
 95            self.variable_bounds or self.variable_integrality or self.linear_constraints
 96        )
 97
 98    def to_proto(self) -> infeasible_subsystem_pb2.ModelSubsetProto:
 99        """Returns an equivalent proto message for this `ModelSubset`."""
100        return infeasible_subsystem_pb2.ModelSubsetProto(
101            variable_bounds={
102                var.id: bounds.to_proto()
103                for (var, bounds) in self.variable_bounds.items()
104            },
105            variable_integrality=sorted(var.id for var in self.variable_integrality),
106            linear_constraints={
107                con.id: bounds.to_proto()
108                for (con, bounds) in self.linear_constraints.items()
109            },
110        )

A subset of a Model's constraints (including variable bounds/integrality).

When returned from solve.compute_infeasible_subsystem, the contained ModelSubsetBounds will all be nonempty.

Attributes:
  • variable_bounds: The upper and/or lower bound constraints on these variables are included in the subset.
  • variable_integrality: The constraint that a variable is integer is included in the subset.
  • linear_constraints: The upper and/or lower bounds from these linear constraints are included in the subset.
ModelSubset( variable_bounds: Mapping[ortools.math_opt.python.model.Variable, ModelSubsetBounds] = immutabledict({}), variable_integrality: FrozenSet[ortools.math_opt.python.model.Variable] = frozenset(), linear_constraints: Mapping[ortools.math_opt.python.model.LinearConstraint, ModelSubsetBounds] = immutabledict({}))
variable_bounds: Mapping[ortools.math_opt.python.model.Variable, ModelSubsetBounds] = immutabledict({})
variable_integrality: FrozenSet[ortools.math_opt.python.model.Variable] = frozenset()
linear_constraints: Mapping[ortools.math_opt.python.model.LinearConstraint, ModelSubsetBounds] = immutabledict({})
def empty(self) -> bool:
84    def empty(self) -> bool:
85        """Returns true if all the nested constraint collections are empty.
86
87        Warning: When `self.variable_bounds` or `self.linear_constraints` contain
88        only ModelSubsetBounds which are themselves empty, this function will return
89        False.
90
91        Returns:
92          True if this is empty.
93        """
94        return not (
95            self.variable_bounds or self.variable_integrality or self.linear_constraints
96        )

Returns true if all the nested constraint collections are empty.

Warning: When self.variable_bounds or self.linear_constraints contain only ModelSubsetBounds which are themselves empty, this function will return False.

Returns:

True if this is empty.

 98    def to_proto(self) -> infeasible_subsystem_pb2.ModelSubsetProto:
 99        """Returns an equivalent proto message for this `ModelSubset`."""
100        return infeasible_subsystem_pb2.ModelSubsetProto(
101            variable_bounds={
102                var.id: bounds.to_proto()
103                for (var, bounds) in self.variable_bounds.items()
104            },
105            variable_integrality=sorted(var.id for var in self.variable_integrality),
106            linear_constraints={
107                con.id: bounds.to_proto()
108                for (con, bounds) in self.linear_constraints.items()
109            },
110        )

Returns an equivalent proto message for this ModelSubset.

113def parse_model_subset(
114    model_subset: infeasible_subsystem_pb2.ModelSubsetProto, mod: model.Model
115) -> ModelSubset:
116    """Returns an equivalent `ModelSubset` to the input proto."""
117    if model_subset.quadratic_constraints:
118        raise NotImplementedError(
119            "quadratic_constraints not yet implemented for ModelSubset in Python"
120        )
121    if model_subset.second_order_cone_constraints:
122        raise NotImplementedError(
123            "second_order_cone_constraints not yet implemented for ModelSubset in"
124            " Python"
125        )
126    if model_subset.sos1_constraints:
127        raise NotImplementedError(
128            "sos1_constraints not yet implemented for ModelSubset in Python"
129        )
130    if model_subset.sos2_constraints:
131        raise NotImplementedError(
132            "sos2_constraints not yet implemented for ModelSubset in Python"
133        )
134    if model_subset.indicator_constraints:
135        raise NotImplementedError(
136            "indicator_constraints not yet implemented for ModelSubset in Python"
137        )
138    return ModelSubset(
139        variable_bounds={
140            mod.get_variable(var_id): parse_model_subset_bounds(bounds)
141            for var_id, bounds in model_subset.variable_bounds.items()
142        },
143        variable_integrality=frozenset(
144            mod.get_variable(var_id) for var_id in model_subset.variable_integrality
145        ),
146        linear_constraints={
147            mod.get_linear_constraint(con_id): parse_model_subset_bounds(bounds)
148            for con_id, bounds in model_subset.linear_constraints.items()
149        },
150    )

Returns an equivalent ModelSubset to the input proto.

@dataclasses.dataclass(frozen=True)
class ComputeInfeasibleSubsystemResult:
153@dataclasses.dataclass(frozen=True)
154class ComputeInfeasibleSubsystemResult:
155    """The result of searching for an infeasible subsystem.
156
157    This is the result of calling `mathopt.compute_infeasible_subsystem()`.
158
159    Attributes:
160      feasibility: If the problem was proven feasible, infeasible, or no
161        conclusion was reached. The fields below are ignored unless the problem
162        was proven infeasible.
163      infeasible_subsystem: Ignored unless `feasibility` is `INFEASIBLE`, a subset
164        of the model that is still infeasible.
165      is_minimal: Ignored unless `feasibility` is `INFEASIBLE`. If True, then the
166        removal of any constraint from `infeasible_subsystem` makes the sub-model
167        feasible. Note that, due to problem transformations MathOpt applies or
168        idiosyncrasies of the solvers contract, the returned infeasible subsystem
169        may not actually be minimal.
170    """
171
172    feasibility: result.FeasibilityStatus = result.FeasibilityStatus.UNDETERMINED
173    infeasible_subsystem: ModelSubset = ModelSubset()
174    is_minimal: bool = False
175
176    def to_proto(
177        self,
178    ) -> infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto:
179        """Returns an equivalent proto for this `ComputeInfeasibleSubsystemResult`."""
180        return infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto(
181            feasibility=self.feasibility.value,
182            infeasible_subsystem=self.infeasible_subsystem.to_proto(),
183            is_minimal=self.is_minimal,
184        )

The result of searching for an infeasible subsystem.

This is the result of calling mathopt.compute_infeasible_subsystem().

Attributes:
  • feasibility: If the problem was proven feasible, infeasible, or no conclusion was reached. The fields below are ignored unless the problem was proven infeasible.
  • infeasible_subsystem: Ignored unless feasibility is INFEASIBLE, a subset of the model that is still infeasible.
  • is_minimal: Ignored unless feasibility is INFEASIBLE. If True, then the removal of any constraint from infeasible_subsystem makes the sub-model feasible. Note that, due to problem transformations MathOpt applies or idiosyncrasies of the solvers contract, the returned infeasible subsystem may not actually be minimal.
ComputeInfeasibleSubsystemResult( feasibility: ortools.math_opt.python.result.FeasibilityStatus = <FeasibilityStatus.UNDETERMINED: 1>, infeasible_subsystem: ModelSubset = ModelSubset(variable_bounds=immutabledict({}), variable_integrality=frozenset(), linear_constraints=immutabledict({})), is_minimal: bool = False)
feasibility: ortools.math_opt.python.result.FeasibilityStatus = <FeasibilityStatus.UNDETERMINED: 1>
infeasible_subsystem: ModelSubset = ModelSubset(variable_bounds=immutabledict({}), variable_integrality=frozenset(), linear_constraints=immutabledict({}))
is_minimal: bool = False
176    def to_proto(
177        self,
178    ) -> infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto:
179        """Returns an equivalent proto for this `ComputeInfeasibleSubsystemResult`."""
180        return infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto(
181            feasibility=self.feasibility.value,
182            infeasible_subsystem=self.infeasible_subsystem.to_proto(),
183            is_minimal=self.is_minimal,
184        )

Returns an equivalent proto for this ComputeInfeasibleSubsystemResult.

def parse_compute_infeasible_subsystem_result( infeasible_system_result: ortools.math_opt.infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto, mod: ortools.math_opt.python.model.Model) -> ComputeInfeasibleSubsystemResult:
187def parse_compute_infeasible_subsystem_result(
188    infeasible_system_result: infeasible_subsystem_pb2.ComputeInfeasibleSubsystemResultProto,
189    mod: model.Model,
190) -> ComputeInfeasibleSubsystemResult:
191    """Returns an equivalent `ComputeInfeasibleSubsystemResult` to the input proto."""
192    return ComputeInfeasibleSubsystemResult(
193        feasibility=result.FeasibilityStatus(infeasible_system_result.feasibility),
194        infeasible_subsystem=parse_model_subset(
195            infeasible_system_result.infeasible_subsystem, mod
196        ),
197        is_minimal=infeasible_system_result.is_minimal,
198    )

Returns an equivalent ComputeInfeasibleSubsystemResult to the input proto.