OR-Tools  9.6
solve_result.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <optional>
17 #include <ostream>
18 #include <sstream>
19 #include <string>
20 #include <utility>
21 #include <vector>
22 
23 #include "absl/container/flat_hash_map.h"
24 #include "absl/status/status.h"
25 #include "absl/status/statusor.h"
26 #include "absl/strings/string_view.h"
27 #include "absl/time/time.h"
28 #include "absl/types/span.h"
29 #include "ortools/base/logging.h"
30 #include "ortools/base/protoutil.h"
34 #include "ortools/math_opt/solution.pb.h"
39 
40 namespace operations_research {
41 namespace math_opt {
42 
43 std::optional<absl::string_view> Enum<FeasibilityStatus>::ToOptString(
45  switch (value) {
47  return "undetermined";
49  return "feasible";
51  return "infeasible";
52  }
53  return std::nullopt;
54 }
55 
56 absl::Span<const FeasibilityStatus> Enum<FeasibilityStatus>::AllValues() {
57  static constexpr FeasibilityStatus kFeasibilityStatus[] = {
61  };
62  return absl::MakeConstSpan(kFeasibilityStatus);
63 }
64 
65 std::optional<absl::string_view> Enum<TerminationReason>::ToOptString(
67  switch (value) {
69  return "optimal";
71  return "infeasible";
73  return "unbounded";
75  return "infeasible_or_unbounded";
77  return "imprecise";
79  return "feasible";
81  return "no_solution_found";
83  return "numerical_error";
85  return "other_error";
86  }
87  return std::nullopt;
88 }
89 
90 absl::Span<const TerminationReason> Enum<TerminationReason>::AllValues() {
91  static constexpr TerminationReason kTerminationReasonValues[] = {
101  };
102  return absl::MakeConstSpan(kTerminationReasonValues);
103 }
104 
105 std::optional<absl::string_view> Enum<Limit>::ToOptString(Limit value) {
106  switch (value) {
108  return "undetermined";
109  case Limit::kIteration:
110  return "iteration";
111  case Limit::kTime:
112  return "time";
113  case Limit::kNode:
114  return "node";
115  case Limit::kSolution:
116  return "solution";
117  case Limit::kMemory:
118  return "memory";
119  case Limit::kCutoff:
120  return "cutoff";
121  case Limit::kObjective:
122  return "objective";
123  case Limit::kNorm:
124  return "norm";
125  case Limit::kInterrupted:
126  return "interrupted";
128  return "slow_progress";
129  case Limit::kOther:
130  return "other";
131  }
132  return std::nullopt;
133 }
134 
135 absl::Span<const Limit> Enum<Limit>::AllValues() {
136  static constexpr Limit kLimitValues[] = {
141  return absl::MakeConstSpan(kLimitValues);
142 }
143 
144 Termination::Termination(const TerminationReason reason, std::string detail)
145  : reason(reason), detail(std::move(detail)) {}
146 
147 Termination Termination::Feasible(const Limit limit, const std::string detail) {
149  termination.limit = limit;
150  return termination;
151 }
152 
154  const std::string detail) {
156  termination.limit = limit;
157  return termination;
158 }
159 
160 TerminationProto Termination::Proto() const {
161  TerminationProto proto;
162  proto.set_reason(EnumToProto(reason));
163  proto.set_limit(EnumToProto(limit));
164  proto.set_detail(detail);
165  return proto;
166 }
167 
171 }
172 
173 absl::StatusOr<Termination> Termination::FromProto(
174  const TerminationProto& termination_proto) {
175  const std::optional<TerminationReason> reason =
176  EnumFromProto(termination_proto.reason());
177  if (!reason.has_value()) {
178  return absl::InvalidArgumentError("reason must be specified");
179  }
180  Termination result(*reason, termination_proto.detail());
181  result.limit = EnumFromProto(termination_proto.limit());
182  return result;
183 }
184 
185 std::ostream& operator<<(std::ostream& ostr, const Termination& termination) {
186  ostr << "{reason: " << termination.reason;
187  if (termination.limit.has_value()) {
188  ostr << ", limit: " << *termination.limit;
189  }
190  if (!termination.detail.empty()) {
191  // TODO(b/200835670): quote detail and escape it properly.
192  ostr << ", detail: " << termination.detail;
193  }
194  ostr << "}";
195  return ostr;
196 }
197 
198 std::string Termination::ToString() const {
199  std::ostringstream stream;
200  stream << *this;
201  return stream.str();
202 }
203 
204 ProblemStatusProto ProblemStatus::Proto() const {
205  ProblemStatusProto proto;
206  proto.set_primal_status(EnumToProto(primal_status));
207  proto.set_dual_status(EnumToProto(dual_status));
208  proto.set_primal_or_dual_infeasible(primal_or_dual_infeasible);
209  return proto;
210 }
211 
212 absl::StatusOr<ProblemStatus> ProblemStatus::FromProto(
213  const ProblemStatusProto& problem_status_proto) {
214  const std::optional<FeasibilityStatus> primal_status =
215  EnumFromProto(problem_status_proto.primal_status());
216  if (!primal_status.has_value()) {
217  return absl::InvalidArgumentError("primal_status must be specified");
218  }
219  const std::optional<FeasibilityStatus> dual_status =
220  EnumFromProto(problem_status_proto.dual_status());
221  if (!dual_status.has_value()) {
222  return absl::InvalidArgumentError("dual_status must be specified");
223  }
224  return ProblemStatus{.primal_status = *primal_status,
225  .dual_status = *dual_status,
226  .primal_or_dual_infeasible =
227  problem_status_proto.primal_or_dual_infeasible()};
228 }
229 
230 std::ostream& operator<<(std::ostream& ostr,
231  const ProblemStatus& problem_status) {
232  ostr << "{primal_status: " << problem_status.primal_status;
233  ostr << ", dual_status: " << problem_status.dual_status;
234  ostr << ", primal_or_dual_infeasible: "
235  << (problem_status.primal_or_dual_infeasible ? "true" : "false");
236  ostr << "}";
237  return ostr;
238 }
239 
240 std::string ProblemStatus::ToString() const {
241  std::ostringstream stream;
242  stream << *this;
243  return stream.str();
244 }
245 
246 absl::StatusOr<SolveStatsProto> SolveStats::Proto() const {
247  SolveStatsProto proto;
249  util_time::EncodeGoogleApiProto(solve_time, proto.mutable_solve_time()))
250  << "invalid solve_time (value must be finite)";
251  proto.set_best_primal_bound(best_primal_bound);
252  proto.set_best_dual_bound(best_dual_bound);
253  *proto.mutable_problem_status() = problem_status.Proto();
254  proto.set_simplex_iterations(simplex_iterations);
255  proto.set_barrier_iterations(barrier_iterations);
256  proto.set_first_order_iterations(first_order_iterations);
257  proto.set_node_count(node_count);
258  return proto;
259 }
260 
261 absl::StatusOr<SolveStats> SolveStats::FromProto(
262  const SolveStatsProto& solve_stats_proto) {
263  SolveStats result;
265  result.solve_time,
266  util_time::DecodeGoogleApiProto(solve_stats_proto.solve_time()),
267  _ << "invalid solve_time");
268  result.best_primal_bound = solve_stats_proto.best_primal_bound();
269  result.best_dual_bound = solve_stats_proto.best_dual_bound();
271  result.problem_status,
272  ProblemStatus::FromProto(solve_stats_proto.problem_status()),
273  _ << "invalid problem_status");
274  result.simplex_iterations = solve_stats_proto.simplex_iterations();
275  result.barrier_iterations = solve_stats_proto.barrier_iterations();
276  result.first_order_iterations = solve_stats_proto.first_order_iterations();
277  result.node_count = solve_stats_proto.node_count();
278  return result;
279 }
280 
281 std::ostream& operator<<(std::ostream& ostr, const SolveStats& solve_stats) {
282  ostr << "{solve_time: " << solve_stats.solve_time;
283  ostr << ", best_primal_bound: "
285  ostr << ", best_dual_bound: "
286  << RoundTripDoubleFormat(solve_stats.best_dual_bound);
287  ostr << ", problem_status: " << solve_stats.problem_status;
288  ostr << ", simplex_iterations: " << solve_stats.simplex_iterations;
289  ostr << ", barrier_iterations: " << solve_stats.barrier_iterations;
290  ostr << ", first_order_iterations: " << solve_stats.first_order_iterations;
291  ostr << ", node_count: " << solve_stats.node_count;
292  ostr << "}";
293  return ostr;
294 }
295 
296 std::string SolveStats::ToString() const {
297  std::ostringstream stream;
298  stream << *this;
299  return stream.str();
300 }
301 
302 absl::Status CheckSolverSpecificOutputEmpty(const SolveResultProto& result) {
303  if (result.solver_specific_output_case() ==
304  SolveResultProto::SOLVER_SPECIFIC_OUTPUT_NOT_SET) {
305  return absl::OkStatus();
306  }
308  << "cannot set solver specific output twice, was already "
309  << static_cast<int>(result.solver_specific_output_case());
310 }
311 
312 absl::StatusOr<SolveResultProto> SolveResult::Proto() const {
313  SolveResultProto result;
314  *result.mutable_termination() = termination.Proto();
315  OR_ASSIGN_OR_RETURN3(*result.mutable_solve_stats(), solve_stats.Proto(),
316  _ << "invalid solve_stats");
317  for (const Solution& solution : solutions) {
318  *result.add_solutions() = solution.Proto();
319  }
320  for (const PrimalRay& primal_ray : primal_rays) {
321  *result.add_primal_rays() = primal_ray.Proto();
322  }
323  for (const DualRay& dual_ray : dual_rays) {
324  *result.add_dual_rays() = dual_ray.Proto();
325  }
326  // See yaqs/5107601535926272 on checking if a proto is empty.
327  if (gscip_solver_specific_output.ByteSizeLong() > 0) {
328  *result.mutable_gscip_output() = gscip_solver_specific_output;
329  }
330  return result;
331 }
332 
333 absl::StatusOr<SolveResult> SolveResult::FromProto(
334  const ModelStorage* model, const SolveResultProto& solve_result_proto) {
336  Termination::FromProto(solve_result_proto.termination()),
337  _ << "invalid termination");
338  SolveResult result(std::move(termination));
340  SolveStats::FromProto(solve_result_proto.solve_stats()),
341  _ << "invalid solve_stats");
342 
343  for (int i = 0; i < solve_result_proto.solutions_size(); ++i) {
345  auto solution,
346  Solution::FromProto(model, solve_result_proto.solutions(i)),
347  _ << "invalid solution at index " << i);
348  result.solutions.push_back(std::move(solution));
349  }
350  for (int i = 0; i < solve_result_proto.primal_rays_size(); ++i) {
352  auto primal_ray,
353  PrimalRay::FromProto(model, solve_result_proto.primal_rays(i)),
354  _ << "invalid primal ray at index " << i);
355  result.primal_rays.push_back(std::move(primal_ray));
356  }
357  for (int i = 0; i < solve_result_proto.dual_rays_size(); ++i) {
359  auto dual_ray,
360  DualRay::FromProto(model, solve_result_proto.dual_rays(i)),
361  _ << "invalid dual ray at index " << i);
362  result.dual_rays.push_back(std::move(dual_ray));
363  }
364  switch (solve_result_proto.solver_specific_output_case()) {
365  case SolveResultProto::kGscipOutput:
366  result.gscip_solver_specific_output = solve_result_proto.gscip_output();
367  return result;
368  case SolveResultProto::SOLVER_SPECIFIC_OUTPUT_NOT_SET:
369  return result;
370  }
372  << "unexpected value of solver_specific_output_case "
373  << solve_result_proto.solver_specific_output_case();
374 }
375 
377  return !solutions.empty() && solutions[0].primal_solution.has_value() &&
378  (solutions[0].primal_solution->feasibility_status ==
380 }
381 
384 }
385 
388  return solutions[0].primal_solution->objective_value;
389 }
390 
391 bool SolveResult::bounded() const {
395 }
396 
399  return solutions[0].primal_solution->variable_values;
400 }
401 
403  CHECK(has_ray());
404  return primal_rays[0].variable_values;
405 }
406 
408  return !solutions.empty() && solutions[0].dual_solution.has_value() &&
409  (solutions[0].dual_solution->feasibility_status ==
411 }
412 
415  return solutions[0].dual_solution->dual_values;
416 }
417 
420  return solutions[0].dual_solution->reduced_costs;
421 }
423  CHECK(has_dual_ray());
424  return dual_rays[0].dual_values;
425 }
426 
428  CHECK(has_dual_ray());
429  return dual_rays[0].reduced_costs;
430 }
431 
433  return !solutions.empty() && solutions[0].basis.has_value();
434 }
435 
437  CHECK(has_basis());
438  return solutions[0].basis->constraint_status;
439 }
440 
442  CHECK(has_basis());
443  return solutions[0].basis->variable_status;
444 }
445 
446 namespace {
447 // Prints only the vector size, not its content.
448 template <typename T>
449 void PrintVectorSize(std::ostream& out, const std::vector<T>& v) {
450  out << '[';
451  if (!v.empty()) {
452  out << v.size() << " available";
453  }
454  out << ']';
455 }
456 } // namespace
457 
458 std::ostream& operator<<(std::ostream& out, const SolveResult& result) {
459  out << "{termination: " << result.termination
460  << ", solve_stats: " << result.solve_stats << ", solutions: ";
461  PrintVectorSize(out, result.solutions);
462  out << ", primal_rays: ";
463  PrintVectorSize(out, result.primal_rays);
464  out << ", dual_rays: ";
465  PrintVectorSize(out, result.dual_rays);
466  {
467  const std::string gscip_specific_output =
469  if (!gscip_specific_output.empty()) {
470  out << ", gscip_solver_specific_output: " << gscip_specific_output;
471  }
472  }
473  out << '}';
474 
475  return out;
476 }
477 
478 } // namespace math_opt
479 } // namespace operations_research
#define RETURN_IF_ERROR(expr)
CpModelProto proto
int64_t value
GRBmodel * model
absl::Status CheckSolverSpecificOutputEmpty(const SolveResultProto &result)
std::optional< typename EnumProto< P >::Cpp > EnumFromProto(const P proto_value)
Definition: enums.h:279
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
Enum< E >::Proto EnumToProto(const std::optional< E > value)
Definition: enums.h:268
Collection of objects used to extend the Constraint Solver library.
std::string ProtobufShortDebugString(const P &message)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition: protoutil.h:42
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition: protoutil.h:27
StatusBuilder InvalidArgumentErrorBuilder()
static absl::StatusOr< DualRay > FromProto(const ModelStorage *model, const DualRayProto &dual_ray_proto)
Definition: solution.cc:137
static std::optional< absl::string_view > ToOptString(E value)
Definition: basis_status.cc:24
static absl::Span< const E > AllValues()
Definition: basis_status.cc:41
static absl::StatusOr< PrimalRay > FromProto(const ModelStorage *model, const PrimalRayProto &primal_ray_proto)
Definition: solution.cc:87
static absl::StatusOr< ProblemStatus > FromProto(const ProblemStatusProto &problem_status_proto)
static absl::StatusOr< Solution > FromProto(const ModelStorage *model, const SolutionProto &solution_proto)
Definition: solution.cc:201
const VariableMap< double > & ray_variable_values() const
const LinearConstraintMap< double > & dual_values() const
const VariableMap< BasisStatus > & variable_status() const
static absl::StatusOr< SolveResult > FromProto(const ModelStorage *model, const SolveResultProto &solve_result_proto)
const LinearConstraintMap< double > & ray_dual_values() const
absl::StatusOr< SolveResultProto > Proto() const
const VariableMap< double > & ray_reduced_costs() const
const LinearConstraintMap< BasisStatus > & constraint_status() const
const VariableMap< double > & variable_values() const
const VariableMap< double > & reduced_costs() const
absl::StatusOr< SolveStatsProto > Proto() const
static absl::StatusOr< SolveStats > FromProto(const SolveStatsProto &solve_stats_proto)
Termination(TerminationReason reason, std::string detail={})
static absl::StatusOr< Termination > FromProto(const TerminationProto &termination_proto)
static Termination Feasible(Limit limit, std::string detail={})
static Termination NoSolutionFound(Limit limit, std::string detail={})
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)