Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
compute_infeasible_subsystem_result.cc
Go to the documentation of this file.
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
15
16#include <cstdint>
17#include <limits>
18#include <optional>
19#include <ostream>
20#include <sstream>
21#include <string>
22#include <tuple>
23
24#include "absl/algorithm/container.h"
25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/status/status.h"
28#include "absl/status/statusor.h"
29#include "absl/strings/str_format.h"
30#include "absl/strings/str_join.h"
31#include "absl/strings/string_view.h"
44#include "ortools/math_opt/infeasible_subsystem.pb.h"
45#include "ortools/math_opt/result.pb.h"
49
51
52constexpr double kInf = std::numeric_limits<double>::infinity();
53
55 const ModelSubsetProto::Bounds& bounds_proto) {
56 return {.lower = bounds_proto.lower(), .upper = bounds_proto.upper()};
57}
58
59ModelSubsetProto::Bounds ModelSubset::Bounds::Proto() const {
60 ModelSubsetProto::Bounds proto;
61 proto.set_lower(lower);
62 proto.set_upper(upper);
63 return proto;
64}
65
66std::ostream& operator<<(std::ostream& out, const ModelSubset::Bounds& bounds) {
67 const auto bool_to_str = [](const bool b) { return b ? "true" : "false"; };
68 out << "{lower: " << bool_to_str(bounds.lower)
69 << ", upper: " << bool_to_str(bounds.upper) << "}";
70 return out;
71}
72
73namespace {
74
75template <typename K>
76absl::Status BoundsMapProtoToCpp(
77 const google::protobuf::Map<int64_t, ModelSubsetProto::Bounds>& source,
78 absl::flat_hash_map<K, ModelSubset::Bounds>& target,
79 const ModelStorage* const model,
80 bool (ModelStorage::* const contains_strong_id)(typename K::IdType id)
81 const,
82 const absl::string_view object_name) {
83 for (const auto& [raw_id, bounds_proto] : source) {
84 const typename K::IdType strong_id(raw_id);
85 if (!(model->*contains_strong_id)(strong_id)) {
87 << "no " << object_name << " with id: " << raw_id;
88 }
89 target.insert(
90 {K(model, strong_id), ModelSubset::Bounds::FromProto(bounds_proto)});
91 }
92 return absl::OkStatus();
93}
94
95template <typename K>
96absl::Status RepeatedIdsProtoToCpp(
97 const google::protobuf::RepeatedField<int64_t>& source,
98 absl::flat_hash_set<K>& target, const ModelStorage* const model,
99 bool (ModelStorage::* const contains_strong_id)(typename K::IdType id)
100 const,
101 const absl::string_view object_name) {
102 for (const int64_t raw_id : source) {
103 const typename K::IdType strong_id(raw_id);
104 if (!(model->*contains_strong_id)(strong_id)) {
106 << "no " << object_name << " with id: " << raw_id;
107 }
108 target.insert(K(model, strong_id));
109 }
110 return absl::OkStatus();
111}
112
113template <typename K>
114google::protobuf::Map<int64_t, ModelSubsetProto::Bounds> BoundsMapCppToProto(
115 const absl::flat_hash_map<K, ModelSubset::Bounds> source) {
116 google::protobuf::Map<int64_t, ModelSubsetProto::Bounds> result;
117 for (const auto& [key, bounds] : source) {
118 result.insert({key.id(), bounds.Proto()});
119 }
120 return result;
121}
122
123template <typename K>
124google::protobuf::RepeatedField<int64_t> RepeatedIdsCppToProto(
125 const absl::flat_hash_set<K>& source) {
126 google::protobuf::RepeatedField<int64_t> result;
127 for (const auto object : source) {
128 result.Add(object.id());
129 }
130 absl::c_sort(result);
131 return result;
132}
133
134} // namespace
135
136absl::StatusOr<ModelSubset> ModelSubset::FromProto(
137 const ModelStorage* const model, const ModelSubsetProto& proto) {
138 ModelSubset model_subset;
139 RETURN_IF_ERROR(BoundsMapProtoToCpp(proto.variable_bounds(),
140 model_subset.variable_bounds, model,
141 &ModelStorage::has_variable, "variable"))
142 << "element of variable_bounds";
143 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
144 proto.variable_integrality(), model_subset.variable_integrality, model,
145 &ModelStorage::has_variable, "variable"))
146 << "element of variable_integrality";
147 RETURN_IF_ERROR(BoundsMapProtoToCpp(
148 proto.linear_constraints(), model_subset.linear_constraints, model,
149 &ModelStorage::has_linear_constraint, "linear constraint"));
150 RETURN_IF_ERROR(BoundsMapProtoToCpp(
151 proto.quadratic_constraints(), model_subset.quadratic_constraints, model,
152 &ModelStorage::has_constraint, "quadratic constraint"));
153 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
154 proto.second_order_cone_constraints(),
155 model_subset.second_order_cone_constraints, model,
156 &ModelStorage::has_constraint, "second-order cone constraint"));
157 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
158 proto.sos1_constraints(), model_subset.sos1_constraints, model,
159 &ModelStorage::has_constraint, "SOS1 constraint"));
160 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
161 proto.sos2_constraints(), model_subset.sos2_constraints, model,
162 &ModelStorage::has_constraint, "SOS2 constraint"));
163 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
164 proto.indicator_constraints(), model_subset.indicator_constraints, model,
165 &ModelStorage::has_constraint, "indicator constraint"));
166 return model_subset;
167}
168
169ModelSubsetProto ModelSubset::Proto() const {
170 ModelSubsetProto proto;
171 *proto.mutable_variable_bounds() = BoundsMapCppToProto(variable_bounds);
172 *proto.mutable_variable_integrality() =
173 RepeatedIdsCppToProto(variable_integrality);
174 *proto.mutable_linear_constraints() = BoundsMapCppToProto(linear_constraints);
175 *proto.mutable_quadratic_constraints() =
176 BoundsMapCppToProto(quadratic_constraints);
177 *proto.mutable_second_order_cone_constraints() =
178 RepeatedIdsCppToProto(second_order_cone_constraints);
179 *proto.mutable_sos1_constraints() = RepeatedIdsCppToProto(sos1_constraints);
180 *proto.mutable_sos2_constraints() = RepeatedIdsCppToProto(sos2_constraints);
181 *proto.mutable_indicator_constraints() =
182 RepeatedIdsCppToProto(indicator_constraints);
183 return proto;
184}
185
187 const ModelStorage* const expected_storage) const {
188 const auto validate_map_keys =
189 [expected_storage](const auto& map,
190 const absl::string_view name) -> absl::Status {
191 for (const auto& [key, unused] : map) {
193 internal::CheckModelStorage(key.storage(), expected_storage))
194 << "invalid key " << key << " in " << name;
195 }
196 return absl::OkStatus();
197 };
198 const auto validate_set_elements =
199 [expected_storage](const auto& set,
200 const absl::string_view name) -> absl::Status {
201 for (const auto entry : set) {
203 internal::CheckModelStorage(entry.storage(), expected_storage))
204 << "invalid entry " << entry << " in " << name;
205 }
206 return absl::OkStatus();
207 };
208
209 RETURN_IF_ERROR(validate_map_keys(variable_bounds, "variable_bounds"));
211 validate_set_elements(variable_integrality, "variable_integrality"));
212 RETURN_IF_ERROR(validate_map_keys(linear_constraints, "linear_constraints"));
214 validate_map_keys(quadratic_constraints, "quadratic_constraints"));
216 "second_order_cone_constraints"));
217 RETURN_IF_ERROR(validate_set_elements(sos1_constraints, "sos1_constraints"));
218 RETURN_IF_ERROR(validate_set_elements(sos2_constraints, "sos2_constraints"));
220 validate_set_elements(indicator_constraints, "indicator_constraints"));
221 return absl::OkStatus();
222}
223
224bool ModelSubset::empty() const {
225 return variable_bounds.empty() && variable_integrality.empty() &&
226 linear_constraints.empty() && quadratic_constraints.empty() &&
228 sos2_constraints.empty() && indicator_constraints.empty();
229}
230
231std::string ModelSubset::ToString() const {
232 std::stringstream str;
233 str << "Model Subset:\n";
234 const auto stream_object = [&str](const auto& object) {
235 str << " " << object << ": " << object.ToString() << "\n";
236 };
237 const auto stream_bounded_object =
238 [&str](const auto& object, const BoundedQuadraticExpression& as_expr,
239 const Bounds& bounds) {
240 if (bounds.empty()) {
241 return;
242 }
243 // We only want to only print the bounds appearing in the subset. The <<
244 // operator for `BoundedQuadraticExpression`s will ignore -/+inf bound
245 // values for the lower/upper bounds, respectively (assuming that at
246 // least one is finite, otherwise it chooses to print one bound
247 // arbitrarily). So, to suppress bounds not in the subset, it suffices
248 // to set their value to the appropriate infinity.
249 const double lb = bounds.lower ? as_expr.lower_bound : -kInf;
250 const double ub = bounds.upper ? as_expr.upper_bound : kInf;
251 str << " " << object << ": " << (lb <= as_expr.expression <= ub)
252 << "\n";
253 };
254
255 str << " Variable bounds:\n";
256 for (const Variable variable : SortedKeys(variable_bounds)) {
257 stream_bounded_object(
258 variable, variable.lower_bound() <= variable <= variable.upper_bound(),
259 variable_bounds.at(variable));
260 }
261 str << " Variable integrality:\n";
262 for (const Variable variable : SortedElements(variable_integrality)) {
263 str << " " << variable << "\n";
264 }
265 str << " Linear constraints:\n";
266 for (const LinearConstraint constraint : SortedKeys(linear_constraints)) {
267 stream_bounded_object(constraint, constraint.AsBoundedLinearExpression(),
268 linear_constraints.at(constraint));
269 }
270 if (!quadratic_constraints.empty()) {
271 str << " Quadratic constraints:\n";
272 for (const QuadraticConstraint constraint :
274 stream_bounded_object(constraint,
275 constraint.AsBoundedQuadraticExpression(),
276 quadratic_constraints.at(constraint));
277 }
278 }
279 if (!second_order_cone_constraints.empty()) {
280 str << " Second-order cone constraints:\n";
281 for (const SecondOrderConeConstraint constraint :
283 stream_object(constraint);
284 }
285 }
286 if (!sos1_constraints.empty()) {
287 str << " SOS1 constraints:\n";
288 for (const Sos1Constraint constraint : SortedElements(sos1_constraints)) {
289 stream_object(constraint);
290 }
291 }
292 if (!sos2_constraints.empty()) {
293 str << " SOS2 constraints:\n";
294 for (const Sos2Constraint constraint : SortedElements(sos2_constraints)) {
295 stream_object(constraint);
296 }
297 }
298 if (!indicator_constraints.empty()) {
299 str << " Indicator constraints:\n";
300 for (const IndicatorConstraint constraint :
302 stream_object(constraint);
303 }
304 }
305 return str.str();
306}
307
308std::ostream& operator<<(std::ostream& out, const ModelSubset& model_subset) {
309 const auto stream_bounds_map = [&out](const auto& map,
310 const absl::string_view name) {
311 out << name << ": {"
312 << absl::StrJoin(SortedKeys(map), ", ",
313 [map](std::string* out, const auto& key) {
314 absl::StrAppendFormat(
315 out, "{%s, %s}", absl::FormatStreamed(key),
316 absl::FormatStreamed(map.at(key)));
317 })
318 << "}";
319 };
320 const auto stream_set = [&out](const auto& set,
321 const absl::string_view name) {
322 out << name << ": {"
323 << absl::StrJoin(SortedElements(set), ", ", absl::StreamFormatter())
324 << "}";
325 };
326
327 out << "{";
328 stream_bounds_map(model_subset.variable_bounds, "variable_bounds");
329 out << ", ";
330 stream_set(model_subset.variable_integrality, "variable_integrality");
331 out << ", ";
332 stream_bounds_map(model_subset.linear_constraints, "linear_constraints");
333 out << ", ";
334 stream_bounds_map(model_subset.quadratic_constraints,
335 "quadratic_constraints");
336 out << ", ";
337 stream_set(model_subset.second_order_cone_constraints,
338 "second_order_cone_constraints");
339 out << ", ";
340 stream_set(model_subset.sos1_constraints, "sos1_constraints");
341 out << ", ";
342 stream_set(model_subset.sos2_constraints, "sos2_constraints");
343 out << ", ";
344 stream_set(model_subset.indicator_constraints, "indicator_constraints");
345 out << "}";
346 return out;
347}
348
349absl::StatusOr<ComputeInfeasibleSubsystemResult>
351 const ModelStorage* const model,
352 const ComputeInfeasibleSubsystemResultProto& result_proto) {
354 const std::optional<FeasibilityStatus> feasibility =
355 EnumFromProto(result_proto.feasibility());
356 if (!feasibility.has_value()) {
357 return absl::InvalidArgumentError(
358 "ComputeInfeasibleSubsystemResultProto.feasibility must be specified");
359 }
360 // We intentionally call this validator after checking `feasibility` so that
361 // we can return a friendlier message for UNSPECIFIED.
364 result.feasibility = *feasibility;
367 ModelSubset::FromProto(model, result_proto.infeasible_subsystem()),
368 _ << "invalid "
369 "ComputeInfeasibleSubsystemResultProto.infeasible_subsystem");
370 result.is_minimal = result_proto.is_minimal();
371 return result;
372}
373
374ComputeInfeasibleSubsystemResultProto ComputeInfeasibleSubsystemResult::Proto()
375 const {
376 ComputeInfeasibleSubsystemResultProto proto;
377 proto.set_feasibility(EnumToProto(feasibility));
378 if (!infeasible_subsystem.empty()) {
379 *proto.mutable_infeasible_subsystem() = infeasible_subsystem.Proto();
380 }
381 proto.set_is_minimal(is_minimal);
382 return proto;
383}
384
386 const ModelStorage* const expected_storage) const {
387 return infeasible_subsystem.CheckModelStorage(expected_storage);
388}
389
390std::ostream& operator<<(std::ostream& out,
391 const ComputeInfeasibleSubsystemResult& result) {
392 out << "{feasibility: " << result.feasibility
393 << ", infeasible_subsystem: " << result.infeasible_subsystem
394 << ", is_minimal: " << (result.is_minimal ? "true" : "false") << "}";
395 return out;
396}
397
398} // namespace operations_research::math_opt
#define RETURN_IF_ERROR(expr)
absl::Status CheckModelStorage(const ModelStorage *const storage, const ModelStorage *const expected_storage)
Definition key_types.h:168
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
std::vector< typename Set::key_type > SortedElements(const Set &set)
Definition key_types.h:110
std::optional< typename EnumProto< P >::Cpp > EnumFromProto(P proto_value)
Definition enums.h:281
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
Enum< E >::Proto EnumToProto(std::optional< E > value)
Definition enums.h:270
absl::Status ValidateComputeInfeasibleSubsystemResultNoModel(const ComputeInfeasibleSubsystemResultProto &result)
Validates the internal consistency of the fields.
std::vector< typename Map::key_type > SortedKeys(const Map &map)
Definition key_types.h:87
StatusBuilder InvalidArgumentErrorBuilder()
A QuadraticExpression with upper and lower bounds.
absl::Status CheckModelStorage(const ModelStorage *expected_storage) const
static absl::StatusOr< ComputeInfeasibleSubsystemResult > FromProto(const ModelStorage *model, const ComputeInfeasibleSubsystemResultProto &result_proto)
FeasibilityStatus feasibility
The primal feasibility status of the model, as determined by the solver.
static Bounds FromProto(const ModelSubsetProto::Bounds &bounds_proto)
absl::flat_hash_map< Variable, Bounds > variable_bounds
absl::flat_hash_set< SecondOrderConeConstraint > second_order_cone_constraints
bool empty() const
True if this object corresponds to the empty subset.
absl::Status CheckModelStorage(const ModelStorage *expected_storage) const
absl::flat_hash_map< LinearConstraint, Bounds > linear_constraints
absl::flat_hash_set< IndicatorConstraint > indicator_constraints
static absl::StatusOr< ModelSubset > FromProto(const ModelStorage *model, const ModelSubsetProto &proto)
absl::flat_hash_map< QuadraticConstraint, Bounds > quadratic_constraints
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)