OR-Tools  9.6
solve_result.h
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 
14 // IWYU pragma: private, include "ortools/math_opt/cpp/math_opt.h"
15 // IWYU pragma: friend "ortools/math_opt/cpp/.*"
16 
17 #ifndef OR_TOOLS_MATH_OPT_CPP_SOLVE_RESULT_H_
18 #define OR_TOOLS_MATH_OPT_CPP_SOLVE_RESULT_H_
19 
20 #include <optional>
21 #include <ostream>
22 #include <string>
23 #include <utility>
24 #include <vector>
25 
26 #include "absl/status/statusor.h"
27 #include "absl/time/time.h"
28 #include "absl/types/span.h"
29 #include "ortools/base/logging.h"
30 #include "ortools/gscip/gscip.pb.h"
31 #include "ortools/math_opt/cpp/enums.h" // IWYU pragma: export
33 #include "ortools/math_opt/cpp/solution.h" // IWYU pragma: export
35 #include "ortools/math_opt/result.pb.h" // IWYU pragma: export
37 
38 namespace operations_research {
39 namespace math_opt {
40 
41 // Problem feasibility status as claimed by the solver (solver is not required
42 // to return a certificate for the claim).
43 enum class FeasibilityStatus {
44  // Solver does not claim a status.
45  kUndetermined = FEASIBILITY_STATUS_UNDETERMINED,
46 
47  // Solver claims the problem is feasible.
48  kFeasible = FEASIBILITY_STATUS_FEASIBLE,
49 
50  // Solver claims the problem is infeasible.
51  kInfeasible = FEASIBILITY_STATUS_INFEASIBLE,
52 };
53 
54 MATH_OPT_DEFINE_ENUM(FeasibilityStatus, FEASIBILITY_STATUS_UNSPECIFIED);
55 
56 // Feasibility status of the primal problem and its dual (or the dual of a
57 // continuous relaxation) as claimed by the solver. The solver is not required
58 // to return a certificate for the claim (e.g. the solver may claim primal
59 // feasibility without returning a primal feasible solutuion). This combined
60 // status gives a comprehensive description of a solver's claims about
61 // feasibility and unboundedness of the solved problem. For instance,
62 // * a feasible status for primal and dual problems indicates the primal is
63 // feasible and bounded and likely has an optimal solution (guaranteed for
64 // problems without non-linear constraints).
65 // * a primal feasible and a dual infeasible status indicates the primal
66 // problem is unbounded (i.e. has arbitrarily good solutions).
67 // Note that a dual infeasible status by itself (i.e. accompanied by an
68 // undetermined primal status) does not imply the primal problem is unbounded as
69 // we could have both problems be infeasible. Also, while a primal and dual
70 // feasible status may imply the existence of an optimal solution, it does not
71 // guarantee the solver has actually found such optimal solution.
72 struct ProblemStatus {
73  // Status for the primal problem.
75 
76  // Status for the dual problem (or for the dual of a continuous relaxation).
78 
79  // If true, the solver claims the primal or dual problem is infeasible, but
80  // it does not know which (or if both are infeasible). Can be true only when
81  // primal_problem_status = dual_problem_status = kUndetermined. This extra
82  // information is often needed when preprocessing determines there is no
83  // optimal solution to the problem (but can't determine if it is due to
84  // infeasibility, unboundedness, or both).
86 
87  // Returns an error if the primal_status or dual_status is unspecified.
88  static absl::StatusOr<ProblemStatus> FromProto(
89  const ProblemStatusProto& problem_status_proto);
90 
91  ProblemStatusProto Proto() const;
92  std::string ToString() const;
93 };
94 
95 std::ostream& operator<<(std::ostream& ostr, const ProblemStatus& status);
96 
97 struct SolveStats {
98  // Elapsed wall clock time as measured by math_opt, roughly the time inside
99  // Solver::Solve(). Note: this does not include work done building the model.
100  absl::Duration solve_time = absl::ZeroDuration();
101 
102  // TODO(b/195295177): Update to add clearer contracts once PDLP's bounds
103  // contract is clarified.
104 
105  // Solver claims the optimal value is equal or better (smaller for
106  // minimization and larger for maximization) than best_primal_bound:
107  // * best_primal_bound is trivial (+inf for minimization and -inf
108  // maximization) when the solver does not claim to have such bound. This
109  // may happen for some solvers (e.g., PDLP, typically continuous solvers)
110  // even when returning optimal (solver could terminate with slightly
111  // infeasible primal solutions).
112  // * best_primal_bound can be closer to the optimal value than the objective
113  // of the best primal feasible solution. In particular, best_primal_bound
114  // may be non-trivial even when no primal feasible solutions are returned.
115  // * best_dual_bound is always better (smaller for minimization and larger
116  // for maximization) than best_primal_bound.
117  double best_primal_bound = 0.0;
118 
119  // Solver claims the optimal value is equal or worse (larger for
120  // minimization and smaller for maximization) than best_dual_bound:
121  // * best_dual_bound is always better (smaller for minimization and larger
122  // for maximization) than best_primal_bound.
123  // * best_dual_bound is trivial (-inf for minimization and +inf
124  // maximization) when the solver does not claim to have such bound.
125  // Similarly to best_primal_bound, this may happen for some solvers even
126  // when returning optimal. MIP solvers will typically report a bound even
127  // if it is imprecise.
128  // * for continuous problems best_dual_bound can be closer to the optimal
129  // value than the objective of the best dual feasible solution. For MIP
130  // one of the first non-trivial values for best_dual_bound is often the
131  // optimal value of the LP relaxation of the MIP.
132  double best_dual_bound = 0.0;
133 
134  // Feasibility statuses for primal and dual problems.
136 
138 
140 
142 
143  int node_count = 0;
144 
145  // Returns an error if converting the problem_status or solve_time fails.
146  static absl::StatusOr<SolveStats> FromProto(
147  const SolveStatsProto& solve_stats_proto);
148 
149  // Will return an error if solve_time is not finite.
150  absl::StatusOr<SolveStatsProto> Proto() const;
151  std::string ToString() const;
152 };
153 
154 std::ostream& operator<<(std::ostream& ostr, const SolveStats& stats);
155 
156 // The reason a call to Solve() terminates.
157 enum class TerminationReason {
158  // A provably optimal solution (up to numerical tolerances) has been found.
159  kOptimal = TERMINATION_REASON_OPTIMAL,
160 
161  // The primal problem has no feasible solutions.
162  kInfeasible = TERMINATION_REASON_INFEASIBLE,
163 
164  // The primal problem is feasible and arbitrarily good solutions can be
165  // found along a primal ray.
166  kUnbounded = TERMINATION_REASON_UNBOUNDED,
167 
168  // The primal problem is either infeasible or unbounded. More details on the
169  // problem status may be available in solve_stats.problem_status. Note that
170  // Gurobi's unbounded status may be mapped here as explained in
171  // go/mathopt-solver-specific#gurobi-inf-or-unb.
172  kInfeasibleOrUnbounded = TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
173 
174  // The problem was solved to one of the criteria above (Optimal, Infeasible,
175  // Unbounded, or InfeasibleOrUnbounded), but one or more tolerances was not
176  // met. Some primal/dual solutions/rays be present, but either they will be
177  // slightly infeasible, or (if the problem was nearly optimal) their may be
178  // a gap between the best solution objective and best objective bound.
179  //
180  // Users can still query primal/dual solutions/rays and solution stats, but
181  // they are responsible for dealing with the numerical imprecision.
182  kImprecise = TERMINATION_REASON_IMPRECISE,
183 
184  // The optimizer reached some kind of limit and a primal feasible solution
185  // is returned. See SolveResultProto.limit_detail for detailed description of
186  // the kind of limit that was reached.
187  kFeasible = TERMINATION_REASON_FEASIBLE,
188 
189  // The optimizer reached some kind of limit and it did not find a primal
190  // feasible solution. See SolveResultProto.limit_detail for detailed
191  // description of the kind of limit that was reached.
192  kNoSolutionFound = TERMINATION_REASON_NO_SOLUTION_FOUND,
193 
194  // The algorithm stopped because it encountered unrecoverable numerical
195  // error. No solution information is available.
196  kNumericalError = TERMINATION_REASON_NUMERICAL_ERROR,
197 
198  // The algorithm stopped because of an error not covered by one of the
199  // statuses defined above. No solution information is available.
200  kOtherError = TERMINATION_REASON_OTHER_ERROR
201 };
202 
203 MATH_OPT_DEFINE_ENUM(TerminationReason, TERMINATION_REASON_UNSPECIFIED);
204 
205 // When a Solve() stops early with TerminationReason kFeasible or
206 // kNoSolutionFound, the specific limit that was hit.
207 enum class Limit {
208  // Used if the underlying solver cannot determine which limit was reached, or
209  // as a null value when we terminated not from a limit (e.g. kOptimal).
210  kUndetermined = LIMIT_UNDETERMINED,
211 
212  // An iterative algorithm stopped after conducting the maximum number of
213  // iterations (e.g. simplex or barrier iterations).
214  kIteration = LIMIT_ITERATION,
215 
216  // The algorithm stopped after a user-specified computation time.
217  kTime = LIMIT_TIME,
218 
219  // A branch-and-bound algorithm stopped because it explored a maximum number
220  // of nodes in the branch-and-bound tree.
221  kNode = LIMIT_NODE,
222 
223  // The algorithm stopped because it found the required number of solutions.
224  // This is often used in MIPs to get the solver to return the first feasible
225  // solution it encounters.
226  kSolution = LIMIT_SOLUTION,
227 
228  // The algorithm stopped because it ran out of memory.
229  kMemory = LIMIT_MEMORY,
230 
231  // The solver was run with a cutoff (e.g. SolveParameters.cutoff_limit was
232  // set) on the objective, indicating that the user did not want any solution
233  // worse than the cutoff, and the solver concluded there were no solutions at
234  // least as good as the cutoff. Typically no further solution information is
235  // provided.
236  kCutoff = LIMIT_CUTOFF,
237 
238  // The algorithm stopped because it found a solution better than a minimum
239  // limit set by the user.
240  kObjective = LIMIT_OBJECTIVE,
241 
242  // The algorithm stopped because the norm of an iterate became too large.
243  kNorm = LIMIT_NORM,
244 
245  // The algorithm stopped because of an interrupt signal or a user interrupt
246  // request.
247  kInterrupted = LIMIT_INTERRUPTED,
248 
249  // The algorithm stopped because it was unable to continue making progress
250  // towards the solution.
251  kSlowProgress = LIMIT_SLOW_PROGRESS,
252 
253  // The algorithm stopped due to a limit not covered by one of the above. Note
254  // that kUndetermined is used when the reason cannot be determined, and kOther
255  // is used when the reason is known but does not fit into any of the above
256  // alternatives.
257  kOther = LIMIT_OTHER
258 };
259 
260 MATH_OPT_DEFINE_ENUM(Limit, LIMIT_UNSPECIFIED);
261 
262 // All information regarding why a call to Solve() terminated.
263 struct Termination {
264  // When the reason is kFeasible or kNoSolutionFound, please use the static
265  // functions Feasible and NoSolutionFound.
266  explicit Termination(TerminationReason reason, std::string detail = {});
267 
268  // Additional information in `limit` when value is kFeasible or
269  // kNoSolutionFound, see `limit` for details.
271 
272  // A Termination within a SolveResult returned by math_opt::Solve() satisfies
273  // some additional invariants:
274  // * limit is set iff reason is kFeasible or kNoSolutionFound.
275  // * if the limit is kCutoff, the termination reason will be
276  // kNoSolutionFound.
277  std::optional<Limit> limit;
278 
279  // Additional typically solver specific information about termination.
280  // Not all solvers can always determine the limit which caused termination,
281  // Limit::kUndetermined is used when the cause cannot be determined.
282  std::string detail;
283 
284  // Returns true if a limit was reached (i.e. if reason is kFeasible or
285  // kNoSolutionFound, and limit is not empty).
286  bool limit_reached() const;
287 
288  // Sets the reason to kFeasible
289  static Termination Feasible(Limit limit, std::string detail = {});
290 
291  // Sets the reason to kNoSolutionFound
292  static Termination NoSolutionFound(Limit limit, std::string detail = {});
293 
294  // Will return an error if termination_proto.reason is UNSPECIFIED.
295  static absl::StatusOr<Termination> FromProto(
296  const TerminationProto& termination_proto);
297  TerminationProto Proto() const;
298  std::string ToString() const;
299 };
300 
301 std::ostream& operator<<(std::ostream& ostr, const Termination& termination);
302 
303 // The result of solving an optimization problem with Solve().
304 struct SolveResult {
306  : termination(std::move(termination)) {}
307 
308  // The reason the solver stopped.
310 
311  // Statistics on the solve process, e.g. running time, iterations.
313 
314  // Basic solutions use, as of Nov 2021:
315  // * All convex optimization solvers (LP, convex QP) return only one
316  // solution as a primal dual pair.
317  // * Only MI(Q)P solvers return more than one solution. MIP solvers do not
318  // return any dual information, or primal infeasible solutions. Solutions
319  // are returned in order of best primal objective first. Gurobi solves
320  // nonconvex QP (integer or continuous) as MIQP.
321 
322  // The general contract for the order of solutions that future solvers should
323  // implement is to order by:
324  // 1. The solutions with a primal feasible solution, ordered by best primal
325  // objective first.
326  // 2. The solutions with a dual feasible solution, ordered by best dual
327  // objective (unknown dual objective is worst)
328  // 3. All remaining solutions can be returned in any order.
329  std::vector<Solution> solutions;
330 
331  // Directions of unbounded primal improvement, or equivalently, dual
332  // infeasibility certificates. Typically provided for TerminationReasons
333  // kUnbounded and kInfeasibleOrUnbounded.
334  std::vector<PrimalRay> primal_rays;
335 
336  // Directions of unbounded dual improvement, or equivalently, primal
337  // infeasibility certificates. Typically provided for TerminationReason
338  // kInfeasible.
339  std::vector<DualRay> dual_rays;
340 
341  // Solver specific output from Gscip. Only populated if Gscip is used.
343 
344  // Returns the SolveResult equivalent of solve_result_proto.
345  //
346  // Returns an error if:
347  // * Any solution or ray cannot be read from proto (e.g. on a subfield,
348  // ids.size != values.size).
349  // * termination or solve_result cannot be read from proto.
350  // See the FromProto() functions for these types for details.
351  //
352  // Note: this is (intentionally) a much weaker test than ValidateResult(). The
353  // guarantees are just strong enough to ensure that a SolveResult and
354  // SolveResultProto can round trip cleanly, e.g. we do not check that a
355  // termination reason optimal implies that there is at least one primal
356  // feasible solution.
357  //
358  // While ValidateResult() is called automatically when you are solving
359  // locally, users who are reading a solution from disk, solving remotely, or
360  // getting their SolveResultProto (or SolveResult) by any other means are
361  // encouraged to either call ValidateResult() themselves, do their own
362  // validation, or not rely on the strong guarantees of ValidateResult()
363  // and just treat SolveResult as a simple struct.
364  static absl::StatusOr<SolveResult> FromProto(
365  const ModelStorage* model, const SolveResultProto& solve_result_proto);
366 
367  // Returns the proto equivalent of this.
368  //
369  // Note that the proto uses a oneof for solver specific output. This method
370  // will fail if multiple solver specific outputs are set.
371  //
372  // TODO(b/231134639): investigate removing the oneof from the proto.
373  absl::StatusOr<SolveResultProto> Proto() const;
374 
375  absl::Duration solve_time() const { return solve_stats.solve_time; }
376 
377  // Indicates if at least one primal feasible solution is available.
378  //
379  // When termination.reason is TerminationReason::kOptimal or
380  // TerminationReason::kFeasible, this is guaranteed to be true and need not be
381  // checked.
382  bool has_primal_feasible_solution() const;
383 
384  // The objective value of the best primal feasible solution. Will CHECK fail
385  // if there are no primal feasible solutions.
386  double objective_value() const;
387 
388  // A bound on the best possible objective value.
389  double best_objective_bound() const;
390 
391  // The variable values from the best primal feasible solution. Will CHECK fail
392  // if there are no primal feasible solutions.
393  const VariableMap<double>& variable_values() const;
394 
395  // Returns true only if the problem has been shown to be feasible and bounded.
396  bool bounded() const;
397 
398  // Indicates if at least one primal ray is available.
399  //
400  // This is NOT guaranteed to be true when termination.reason is
401  // TerminationReason::kUnbounded or TerminationReason::kInfeasibleOrUnbounded.
402  bool has_ray() const { return !primal_rays.empty(); }
403 
404  // The variable values from the first primal ray. Will CHECK fail if there
405  // are no primal rays.
407 
408  // Indicates if the best solution has an associated dual feasible solution.
409  //
410  // This is NOT guaranteed to be true when termination.reason is
411  // TerminationReason::kOptimal. It also may be true even when the best
412  // solution does not have an associated primal feasible solution.
413  bool has_dual_feasible_solution() const;
414 
415  // The dual values associated to the best solution.
416  //
417  // If there is at least one primal feasible solution, this corresponds to the
418  // dual values associated to the best primal feasible solution. Will CHECK
419  // fail if the best solution does not have an associated dual feasible
420  // solution.
422 
423  // The reduced costs associated to the best solution.
424  //
425  // If there is at least one primal feasible solution, this corresponds to the
426  // reduced costs associated to the best primal feasible solution. Will CHECK
427  // fail if the best solution does not have an associated dual feasible
428  // solution.
429  const VariableMap<double>& reduced_costs() const;
430 
431  // Indicates if at least one dual ray is available.
432  //
433  // This is NOT guaranteed to be true when termination.reason is
434  // TerminationReason::kInfeasible.
435  bool has_dual_ray() const { return !dual_rays.empty(); }
436 
437  // The dual values from the first dual ray. Will CHECK fail if there
438  // are no dual rays.
440 
441  // The reduced from the first dual ray. Will CHECK fail if there
442  // are no dual rays.
443  const VariableMap<double>& ray_reduced_costs() const;
444 
445  // Indicates if the best solution has an associated basis.
446  bool has_basis() const;
447 
448  // The constraint basis status for the best solution.
450 
451  // The variable basis status for the best solution.
453 };
454 
455 // Prints a summary of the solve result on a single line.
456 //
457 // This prints the number of available solutions and rays instead of their
458 // values.
459 //
460 // Printing the whole solution could be problematic for huge models.
461 std::ostream& operator<<(std::ostream& out, const SolveResult& result);
462 
463 } // namespace math_opt
464 } // namespace operations_research
465 
466 #endif // OR_TOOLS_MATH_OPT_CPP_SOLVE_RESULT_H_
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
MATH_OPT_DEFINE_ENUM(BasisStatus, BASIS_STATUS_UNSPECIFIED)
Collection of objects used to extend the Constraint Solver library.
static absl::StatusOr< ProblemStatus > FromProto(const ProblemStatusProto &problem_status_proto)
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={})