OR-Tools  9.2
result_validator.cc
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
15
16#include <limits>
17#include <string>
18
19#include "absl/status/status.h"
20#include "absl/strings/str_cat.h"
22#include "ortools/math_opt/model_parameters.pb.h"
23#include "ortools/math_opt/result.pb.h"
24#include "ortools/math_opt/solution.pb.h"
29
30namespace operations_research {
31namespace math_opt {
32namespace {
33
34constexpr double kInf = std::numeric_limits<double>::infinity();
35
36absl::Status ValidateSolutionStatus(const SolutionStatusProto& status) {
37 if (!SolutionStatusProto_IsValid(status)) {
38 return absl::InvalidArgumentError(absl::StrCat("status = ", status));
39 }
40 if (status == SOLUTION_STATUS_UNSPECIFIED) {
41 return absl::InvalidArgumentError("status = SOLUTION_STATUS_UNSPECIFIED");
42 }
43 return absl::OkStatus();
44}
45
46absl::Status ValidateTermination(const TerminationProto& termination) {
47 if (termination.reason() == TERMINATION_REASON_UNSPECIFIED) {
48 return absl::InvalidArgumentError("termination reason must be specified");
49 }
50 if (termination.reason() == TERMINATION_REASON_FEASIBLE ||
51 termination.reason() == TERMINATION_REASON_NO_SOLUTION_FOUND) {
52 if (termination.limit() == LIMIT_UNSPECIFIED) {
53 return absl::InvalidArgumentError(
54 absl::StrCat("for reason ", ProtoEnumToString(termination.reason()),
55 ", limit must be specified"));
56 }
57 if (termination.limit() == LIMIT_CUTOFF &&
58 termination.reason() == TERMINATION_REASON_FEASIBLE) {
59 return absl::InvalidArgumentError(
60 "For LIMIT_CUTOFF expected no solutions");
61 }
62 } else {
63 if (termination.limit() != LIMIT_UNSPECIFIED) {
64 return absl::InvalidArgumentError(
65 absl::StrCat("for reason:", ProtoEnumToString(termination.reason()),
66 ", limit should be unspecified, but was set to: ",
67 ProtoEnumToString(termination.limit())));
68 }
69 }
70 return absl::OkStatus();
71}
72
73bool HasPrimalFeasibleSolution(const SolutionProto& solution) {
74 return solution.has_primal_solution() &&
75 solution.primal_solution().feasibility_status() ==
76 SOLUTION_STATUS_FEASIBLE;
77}
78
79bool HasPrimalFeasibleSolution(const SolveResultProto& result) {
80 // Assumes first solution is primal feasible if there is any primal solution.
81 return !result.solutions().empty() &&
82 HasPrimalFeasibleSolution(result.solutions(0));
83}
84
85bool HasDualFeasibleSolution(const SolutionProto& solution) {
86 return solution.has_dual_solution() &&
87 solution.dual_solution().feasibility_status() ==
88 SOLUTION_STATUS_FEASIBLE;
89}
90
91bool HasDualFeasibleSolution(const SolveResultProto& result) {
92 for (const auto& solution : result.solutions()) {
93 if (HasDualFeasibleSolution(solution)) {
94 return true;
95 }
96 }
97 return false;
98}
99
100absl::Status ValidateSolutions(
101 const google::protobuf::RepeatedPtrField<SolutionProto>& solutions,
102 const ModelSolveParametersProto& parameters,
103 const ModelSummary& model_summary) {
104 // Validate individual solutions
105 for (int i = 0; i < solutions.size(); ++i) {
106 RETURN_IF_ERROR(ValidateSolution(solutions[i], parameters, model_summary))
107 << "invalid solutions[" << i << "]";
108 }
109
110 if (solutions.empty()) return absl::OkStatus();
111
112 // Validate solution order.
113 // TODO(b/204457524): check objective ordering when possible.
114 bool previous_primal_feasible = HasPrimalFeasibleSolution(solutions[0]);
115 bool previous_dual_feasible = HasDualFeasibleSolution(solutions[0]);
116 for (int i = 1; i < solutions.size(); ++i) {
117 const bool current_primal_feasible =
118 HasPrimalFeasibleSolution(solutions[i]);
119 const bool current_dual_feasible = HasDualFeasibleSolution(solutions[i]);
120 // Primal-feasible solutions must appear first.
121 if (current_primal_feasible && !previous_primal_feasible) {
122 return absl::InvalidArgumentError(
123 "primal solution ordering not satisfied");
124 }
125 // Dual-feasible solutions must appear first within the groups of
126 // primal-feasible and other solutions. Equivalently, a dual-feasible
127 // solution must be preceded by a dual-feasible solution, except when we
128 // switch from the group of primal-feasible solutions to the group of other
129 // solutions.
130 if (current_dual_feasible && !previous_dual_feasible) {
131 if (!(previous_primal_feasible && !current_primal_feasible)) {
132 return absl::InvalidArgumentError(
133 "dual solution ordering not satisfied");
134 }
135 }
136 previous_primal_feasible = current_primal_feasible;
137 previous_dual_feasible = current_dual_feasible;
138 }
139 return absl::OkStatus();
140}
141
142absl::Status RequireNoPrimalFeasibleSolution(const SolveResultProto& result) {
143 if (HasPrimalFeasibleSolution(result)) {
144 return absl::InvalidArgumentError(
145 "expected no primal feasible solution, but one was returned");
146 }
147
148 return absl::OkStatus();
149}
150
151absl::Status RequireNoDualFeasibleSolution(const SolveResultProto& result) {
152 if (HasDualFeasibleSolution(result)) {
153 return absl::InvalidArgumentError(
154 "expected no dual feasible solution, but one was returned");
155 }
156
157 return absl::OkStatus();
158}
159} // namespace
160
161absl::Status CheckHasPrimalSolution(const SolveResultProto& result) {
162 if (!HasPrimalFeasibleSolution(result)) {
163 return absl::InvalidArgumentError(
164 "primal feasible solution expected, but not found");
165 }
166
167 return absl::OkStatus();
168}
169
171 const SolveResultProto& result) {
172 if (result.solve_stats().problem_status().primal_status() !=
173 FEASIBILITY_STATUS_FEASIBLE &&
174 HasPrimalFeasibleSolution(result)) {
175 return absl::InvalidArgumentError(
176 "primal feasibility status is not FEASIBILITY_STATUS_FEASIBLE, but "
177 "primal feasible solution is returned.");
178 }
179 return absl::OkStatus();
180}
181
183 const SolveResultProto& result) {
184 if (result.solve_stats().problem_status().dual_status() !=
185 FEASIBILITY_STATUS_FEASIBLE &&
186 HasDualFeasibleSolution(result)) {
187 return absl::InvalidArgumentError(
188 "dual feasibility status is not FEASIBILITY_STATUS_FEASIBLE, but "
189 "dual feasible solution is returned.");
190 }
191 return absl::OkStatus();
192}
193
194// Assumes ValidateTermination has been called and ValidateProblemStatusProto
195// has been called on result.solve_stats.problem_status.
196// TODO(b/212685946): add ray checks
197absl::Status ValidateTerminationConsistency(const SolveResultProto& result) {
198 const ProblemStatusProto status = result.solve_stats().problem_status();
199 switch (result.termination().reason()) {
200 case TERMINATION_REASON_OPTIMAL:
201 RETURN_IF_ERROR(CheckPrimalStatusIs(status, FEASIBILITY_STATUS_FEASIBLE));
202 RETURN_IF_ERROR(CheckDualStatusIs(status, FEASIBILITY_STATUS_FEASIBLE));
204 // Dual feasible solution is not required.
205 // Primal/dual requirements imply primal/dual solution-status consistency.
206 return absl::OkStatus();
207 // TODO(b/211677729): update when TERMINATION_REASON_FEASIBLE is added.
208 case TERMINATION_REASON_INFEASIBLE:
210 CheckPrimalStatusIs(status, FEASIBILITY_STATUS_INFEASIBLE));
211 RETURN_IF_ERROR(RequireNoPrimalFeasibleSolution(result));
212 // Primal requirements imply primal solution-status consistency.
213 // No dual requirements so we check consistency.
215 return absl::OkStatus();
216 case TERMINATION_REASON_UNBOUNDED:
217 RETURN_IF_ERROR(CheckPrimalStatusIs(status, FEASIBILITY_STATUS_FEASIBLE));
218 RETURN_IF_ERROR(CheckDualStatusIs(status, FEASIBILITY_STATUS_INFEASIBLE));
219 // No primal feasible solution is required.
220 RETURN_IF_ERROR(RequireNoDualFeasibleSolution(result));
221 // Primal/dual requirements imply primal/dual solution-status consistency.
222 return absl::OkStatus();
223 case TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED:
225 CheckPrimalStatusIs(status, FEASIBILITY_STATUS_UNDETERMINED));
227 CheckDualStatusIs(status, FEASIBILITY_STATUS_INFEASIBLE,
228 /*primal_or_dual_infeasible_also_ok=*/true));
229 RETURN_IF_ERROR(RequireNoPrimalFeasibleSolution(result));
230 RETURN_IF_ERROR(RequireNoDualFeasibleSolution(result));
231 // Primal/dual requirements imply primal/dual solution-status consistency.
232 // Note if primal status was not FEASIBILITY_STATUS_UNDETERMINED, then
233 // primal_or_dual_infeasible must be false and dual status would be
234 // FEASIBILITY_STATUS_INFEASIBLE. Then if primal status was
235 // FEASIBILITY_STATUS_INFEASIBLE we would have
236 // TERMINATION_REASON_INFEASIBLE and if it was FEASIBILITY_STATUS_FEASIBLE
237 // we would have TERMINATION_REASON_UNBOUNDED.
238 return absl::OkStatus();
239 case TERMINATION_REASON_IMPRECISE:
240 // TODO(b/211679884): update when imprecise solutions are added.
241 return absl::OkStatus();
242 case TERMINATION_REASON_FEASIBLE:
243 RETURN_IF_ERROR(CheckPrimalStatusIs(status, FEASIBILITY_STATUS_FEASIBLE));
246 CheckDualStatusIsNot(status, FEASIBILITY_STATUS_INFEASIBLE));
247 // Primal requirement implies primal solution-status consistency so we
248 // only check dual consistency.
250 // Note if dual status was FEASIBILITY_STATUS_INFEASIBLE, then we would
251 // have TERMINATION_REASON_UNBOUNDED (For MIP this follows tha assumption
252 // that every floating point ray can be scaled to be integer).
253 return absl::OkStatus();
254 case TERMINATION_REASON_NO_SOLUTION_FOUND:
255 RETURN_IF_ERROR(RequireNoPrimalFeasibleSolution(result));
257 CheckPrimalStatusIsNot(status, FEASIBILITY_STATUS_INFEASIBLE));
258 // Primal requirement implies primal solution-status consistency so we
259 // only check dual consistency.
261 // Note if primal status was FEASIBILITY_STATUS_INFEASIBLE, then we would
262 // have TERMINATION_REASON_INFEASIBLE.
263 return absl::OkStatus();
264 case TERMINATION_REASON_NUMERICAL_ERROR:
265 case TERMINATION_REASON_OTHER_ERROR: {
267 CheckPrimalStatusIs(status, FEASIBILITY_STATUS_UNDETERMINED));
269 CheckDualStatusIs(status, FEASIBILITY_STATUS_UNDETERMINED));
270 if (!result.solutions().empty()) {
271 return absl::InvalidArgumentError(
272 absl::StrCat("termination reason is ",
273 ProtoEnumToString(result.termination().reason()),
274 ", but solutions are available"));
275 }
276 if (result.solve_stats().problem_status().primal_or_dual_infeasible()) {
277 return absl::InvalidArgumentError(absl::StrCat(
278 "termination reason is ",
279 ProtoEnumToString(result.termination().reason()),
280 ", but solve_stats.problem_status.primal_or_dual_infeasible = "
281 "true"));
282 }
283 // Primal/dual requirements imply primal/dual solution-status consistency.
284 }
285 return absl::OkStatus();
286 default:
287 LOG(FATAL) << ProtoEnumToString(result.termination().reason())
288 << " not implemented";
289 }
290
291 return absl::OkStatus();
292}
293
294absl::Status ValidateResult(const SolveResultProto& result,
295 const ModelSolveParametersProto& parameters,
296 const ModelSummary& model_summary) {
297 RETURN_IF_ERROR(ValidateTermination(result.termination()));
298 RETURN_IF_ERROR(ValidateSolveStats(result.solve_stats()));
300 ValidateSolutions(result.solutions(), parameters, model_summary));
301
302 for (int i = 0; i < result.primal_rays_size(); ++i) {
303 RETURN_IF_ERROR(ValidatePrimalRay(result.primal_rays(i),
304 parameters.variable_values_filter(),
305 model_summary))
306 << "Invalid primal_rays[" << i << "]";
307 }
308 for (int i = 0; i < result.dual_rays_size(); ++i) {
310 ValidateDualRay(result.dual_rays(i), parameters, model_summary))
311 << "Invalid dual_rays[" << i << "]";
312 }
313
315 << "inconsistent termination reason "
316 << ProtoEnumToString(result.termination().reason());
317
318 return absl::OkStatus();
319}
320
321} // namespace math_opt
322} // namespace operations_research
#define LOG(severity)
Definition: base/logging.h:420
SatParameters parameters
absl::Status status
Definition: g_gurobi.cc:35
const int FATAL
Definition: log_severity.h:32
absl::Status ValidateResult(const SolveResultProto &result, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status CheckDualSolutionAndStatusConsistency(const SolveResultProto &result)
absl::Status ValidateTerminationConsistency(const SolveResultProto &result)
absl::Status CheckPrimalStatusIs(const ProblemStatusProto &status, const FeasibilityStatusProto required_status)
absl::Status CheckDualStatusIs(const ProblemStatusProto &status, const FeasibilityStatusProto required_status, const bool primal_or_dual_infeasible_also_ok)
absl::Status CheckPrimalSolutionAndStatusConsistency(const SolveResultProto &result)
absl::Status ValidatePrimalRay(const PrimalRayProto &primal_ray, const SparseVectorFilterProto &filter, const ModelSummary &model_summary)
absl::Status CheckDualStatusIsNot(const ProblemStatusProto &status, const FeasibilityStatusProto forbidden_status)
absl::Status CheckHasPrimalSolution(const SolveResultProto &result)
absl::Status CheckPrimalStatusIsNot(const ProblemStatusProto &status, const FeasibilityStatusProto forbidden_status)
absl::Status ValidateSolveStats(const SolveStatsProto &solve_stats)
absl::Status ValidateDualRay(const DualRayProto &dual_ray, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status ValidateSolution(const SolutionProto &solution, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
Collection of objects used to extend the Constraint Solver library.
std::string ProtoEnumToString(ProtoEnumType enum_value)
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29