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