Google OR-Tools v9.11
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-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
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) const,
81 const absl::string_view object_name) {
82 for (const auto& [raw_id, bounds_proto] : source) {
83 const typename K::IdType strong_id(raw_id);
84 if (!(model->*contains_strong_id)(strong_id)) {
86 << "no " << object_name << " with id: " << raw_id;
87 }
88 target.insert(
89 {K(model, strong_id), ModelSubset::Bounds::FromProto(bounds_proto)});
90 }
91 return absl::OkStatus();
92}
93
94template <typename K>
95absl::Status RepeatedIdsProtoToCpp(
96 const google::protobuf::RepeatedField<int64_t>& source,
97 absl::flat_hash_set<K>& target, const ModelStorage* const model,
98 bool (ModelStorage::*const contains_strong_id)(typename K::IdType id) const,
99 const absl::string_view object_name) {
100 for (const int64_t raw_id : source) {
101 const typename K::IdType strong_id(raw_id);
102 if (!(model->*contains_strong_id)(strong_id)) {
104 << "no " << object_name << " with id: " << raw_id;
105 }
106 target.insert(K(model, strong_id));
107 }
108 return absl::OkStatus();
109}
110
111template <typename K>
112google::protobuf::Map<int64_t, ModelSubsetProto::Bounds> BoundsMapCppToProto(
113 const absl::flat_hash_map<K, ModelSubset::Bounds> source) {
114 google::protobuf::Map<int64_t, ModelSubsetProto::Bounds> result;
115 for (const auto& [key, bounds] : source) {
116 result.insert({key.id(), bounds.Proto()});
117 }
118 return result;
119}
120
121template <typename K>
122google::protobuf::RepeatedField<int64_t> RepeatedIdsCppToProto(
123 const absl::flat_hash_set<K>& source) {
124 google::protobuf::RepeatedField<int64_t> result;
125 for (const auto object : source) {
126 result.Add(object.id());
127 }
128 absl::c_sort(result);
129 return result;
130}
131
132} // namespace
133
134absl::StatusOr<ModelSubset> ModelSubset::FromProto(
135 const ModelStorage* const model, const ModelSubsetProto& proto) {
136 ModelSubset model_subset;
137 RETURN_IF_ERROR(BoundsMapProtoToCpp(proto.variable_bounds(),
138 model_subset.variable_bounds, model,
139 &ModelStorage::has_variable, "variable"))
140 << "element of variable_bounds";
141 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
142 proto.variable_integrality(), model_subset.variable_integrality, model,
143 &ModelStorage::has_variable, "variable"))
144 << "element of variable_integrality";
145 RETURN_IF_ERROR(BoundsMapProtoToCpp(
146 proto.linear_constraints(), model_subset.linear_constraints, model,
147 &ModelStorage::has_linear_constraint, "linear constraint"));
148 RETURN_IF_ERROR(BoundsMapProtoToCpp(
149 proto.quadratic_constraints(), model_subset.quadratic_constraints, model,
150 &ModelStorage::has_constraint, "quadratic constraint"));
151 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
152 proto.second_order_cone_constraints(),
154 &ModelStorage::has_constraint, "second-order cone constraint"));
155 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
156 proto.sos1_constraints(), model_subset.sos1_constraints, model,
157 &ModelStorage::has_constraint, "SOS1 constraint"));
158 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
159 proto.sos2_constraints(), model_subset.sos2_constraints, model,
160 &ModelStorage::has_constraint, "SOS2 constraint"));
161 RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
162 proto.indicator_constraints(), model_subset.indicator_constraints, model,
163 &ModelStorage::has_constraint, "indicator constraint"));
164 return model_subset;
165}
166
167ModelSubsetProto ModelSubset::Proto() const {
168 ModelSubsetProto proto;
169 *proto.mutable_variable_bounds() = BoundsMapCppToProto(variable_bounds);
170 *proto.mutable_variable_integrality() =
171 RepeatedIdsCppToProto(variable_integrality);
172 *proto.mutable_linear_constraints() = BoundsMapCppToProto(linear_constraints);
173 *proto.mutable_quadratic_constraints() =
174 BoundsMapCppToProto(quadratic_constraints);
175 *proto.mutable_second_order_cone_constraints() =
176 RepeatedIdsCppToProto(second_order_cone_constraints);
177 *proto.mutable_sos1_constraints() = RepeatedIdsCppToProto(sos1_constraints);
178 *proto.mutable_sos2_constraints() = RepeatedIdsCppToProto(sos2_constraints);
179 *proto.mutable_indicator_constraints() =
180 RepeatedIdsCppToProto(indicator_constraints);
181 return proto;
182}
183
185 const ModelStorage* const expected_storage) const {
186 const auto validate_map_keys =
187 [expected_storage](const auto& map,
188 const absl::string_view name) -> absl::Status {
189 for (const auto& [key, unused] : map) {
191 internal::CheckModelStorage(key.storage(), expected_storage))
192 << "invalid key " << key << " in " << name;
193 }
194 return absl::OkStatus();
195 };
196 const auto validate_set_elements =
197 [expected_storage](const auto& set,
198 const absl::string_view name) -> absl::Status {
199 for (const auto entry : set) {
201 internal::CheckModelStorage(entry.storage(), expected_storage))
202 << "invalid entry " << entry << " in " << name;
203 }
204 return absl::OkStatus();
205 };
206
207 RETURN_IF_ERROR(validate_map_keys(variable_bounds, "variable_bounds"));
209 validate_set_elements(variable_integrality, "variable_integrality"));
210 RETURN_IF_ERROR(validate_map_keys(linear_constraints, "linear_constraints"));
212 validate_map_keys(quadratic_constraints, "quadratic_constraints"));
214 "second_order_cone_constraints"));
215 RETURN_IF_ERROR(validate_set_elements(sos1_constraints, "sos1_constraints"));
216 RETURN_IF_ERROR(validate_set_elements(sos2_constraints, "sos2_constraints"));
218 validate_set_elements(indicator_constraints, "indicator_constraints"));
219 return absl::OkStatus();
220}
221
222bool ModelSubset::empty() const {
223 return variable_bounds.empty() && variable_integrality.empty() &&
224 linear_constraints.empty() && quadratic_constraints.empty() &&
226 sos2_constraints.empty() && indicator_constraints.empty();
227}
228
229std::string ModelSubset::ToString() const {
230 std::stringstream str;
231 str << "Model Subset:\n";
232 const auto stream_object = [&str](const auto& object) {
233 str << " " << object << ": " << object.ToString() << "\n";
234 };
235 const auto stream_bounded_object =
236 [&str](const auto& object, const BoundedQuadraticExpression& as_expr,
237 const Bounds& bounds) {
238 if (bounds.empty()) {
239 return;
240 }
241 // We only want to only print the bounds appearing in the subset. The <<
242 // operator for `BoundedQuadraticExpression`s will ignore -/+inf bound
243 // values for the lower/upper bounds, respectively (assuming that at
244 // least one is finite, otherwise it chooses to print one bound
245 // arbitrarily). So, to suppress bounds not in the subset, it suffices
246 // to set their value to the appropriate infinity.
247 const double lb = bounds.lower ? as_expr.lower_bound : -kInf;
248 const double ub = bounds.upper ? as_expr.upper_bound : kInf;
249 str << " " << object << ": " << (lb <= as_expr.expression <= ub)
250 << "\n";
251 };
252
253 str << " Variable bounds:\n";
254 for (const Variable variable : SortedKeys(variable_bounds)) {
255 stream_bounded_object(
256 variable, variable.lower_bound() <= variable <= variable.upper_bound(),
257 variable_bounds.at(variable));
258 }
259 str << " Variable integrality:\n";
260 for (const Variable variable : SortedElements(variable_integrality)) {
261 str << " " << variable << "\n";
262 }
263 str << " Linear constraints:\n";
264 for (const LinearConstraint constraint : SortedKeys(linear_constraints)) {
265 stream_bounded_object(constraint, constraint.AsBoundedLinearExpression(),
266 linear_constraints.at(constraint));
267 }
268 if (!quadratic_constraints.empty()) {
269 str << " Quadratic constraints:\n";
270 for (const QuadraticConstraint constraint :
272 stream_bounded_object(constraint,
273 constraint.AsBoundedQuadraticExpression(),
274 quadratic_constraints.at(constraint));
275 }
276 }
277 if (!second_order_cone_constraints.empty()) {
278 str << " Second-order cone constraints:\n";
279 for (const SecondOrderConeConstraint constraint :
281 stream_object(constraint);
282 }
283 }
284 if (!sos1_constraints.empty()) {
285 str << " SOS1 constraints:\n";
286 for (const Sos1Constraint constraint : SortedElements(sos1_constraints)) {
287 stream_object(constraint);
288 }
289 }
290 if (!sos2_constraints.empty()) {
291 str << " SOS2 constraints:\n";
292 for (const Sos2Constraint constraint : SortedElements(sos2_constraints)) {
293 stream_object(constraint);
294 }
295 }
296 if (!indicator_constraints.empty()) {
297 str << " Indicator constraints:\n";
298 for (const IndicatorConstraint constraint :
300 stream_object(constraint);
301 }
302 }
303 return str.str();
304}
305
306std::ostream& operator<<(std::ostream& out, const ModelSubset& model_subset) {
307 const auto stream_bounds_map = [&out](const auto& map,
308 const absl::string_view name) {
309 out << name << ": {"
310 << absl::StrJoin(SortedKeys(map), ", ",
311 [map](std::string* out, const auto& key) {
312 absl::StrAppendFormat(
313 out, "{%s, %s}", absl::FormatStreamed(key),
314 absl::FormatStreamed(map.at(key)));
315 })
316 << "}";
317 };
318 const auto stream_set = [&out](const auto& set,
319 const absl::string_view name) {
320 out << name << ": {"
321 << absl::StrJoin(SortedElements(set), ", ", absl::StreamFormatter())
322 << "}";
323 };
324
325 out << "{";
326 stream_bounds_map(model_subset.variable_bounds, "variable_bounds");
327 out << ", ";
328 stream_set(model_subset.variable_integrality, "variable_integrality");
329 out << ", ";
330 stream_bounds_map(model_subset.linear_constraints, "linear_constraints");
331 out << ", ";
332 stream_bounds_map(model_subset.quadratic_constraints,
333 "quadratic_constraints");
334 out << ", ";
335 stream_set(model_subset.second_order_cone_constraints,
336 "second_order_cone_constraints");
337 out << ", ";
338 stream_set(model_subset.sos1_constraints, "sos1_constraints");
339 out << ", ";
340 stream_set(model_subset.sos2_constraints, "sos2_constraints");
341 out << ", ";
342 stream_set(model_subset.indicator_constraints, "indicator_constraints");
343 out << "}";
344 return out;
345}
346
347absl::StatusOr<ComputeInfeasibleSubsystemResult>
349 const ModelStorage* const model,
350 const ComputeInfeasibleSubsystemResultProto& result_proto) {
352 const std::optional<FeasibilityStatus> feasibility =
353 EnumFromProto(result_proto.feasibility());
354 if (!feasibility.has_value()) {
355 return absl::InvalidArgumentError(
356 "ComputeInfeasibleSubsystemResultProto.feasibility must be specified");
357 }
358 // We intentionally call this validator after checking `feasibility` so that
359 // we can return a friendlier message for UNSPECIFIED.
362 result.feasibility = *feasibility;
365 ModelSubset::FromProto(model, result_proto.infeasible_subsystem()),
366 _ << "invalid "
367 "ComputeInfeasibleSubsystemResultProto.infeasible_subsystem");
368 result.is_minimal = result_proto.is_minimal();
369 return result;
370}
371
372ComputeInfeasibleSubsystemResultProto ComputeInfeasibleSubsystemResult::Proto()
373 const {
374 ComputeInfeasibleSubsystemResultProto proto;
375 proto.set_feasibility(EnumToProto(feasibility));
376 if (!infeasible_subsystem.empty()) {
377 *proto.mutable_infeasible_subsystem() = infeasible_subsystem.Proto();
378 }
379 proto.set_is_minimal(is_minimal);
380 return proto;
381}
382
384 const ModelStorage* const expected_storage) const {
385 return infeasible_subsystem.CheckModelStorage(expected_storage);
386}
387
388std::ostream& operator<<(std::ostream& out,
389 const ComputeInfeasibleSubsystemResult& result) {
390 out << "{feasibility: " << result.feasibility
391 << ", infeasible_subsystem: " << result.infeasible_subsystem
392 << ", is_minimal: " << (result.is_minimal ? "true" : "false") << "}";
393 return out;
394}
395
396} // namespace operations_research::math_opt
#define RETURN_IF_ERROR(expr)
int64_t b
Definition table.cc:45
CpModelProto proto
The output proto.
const std::string name
A name for logging purposes.
double lower
double upper
GRBmodel * model
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)