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