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