Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
result_validator.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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
18#include "absl/status/status.h"
19#include "google/protobuf/repeated_ptr_field.h"
24#include "ortools/math_opt/model_parameters.pb.h"
25#include "ortools/math_opt/result.pb.h"
26#include "ortools/math_opt/solution.pb.h"
31
32namespace operations_research {
33namespace math_opt {
34namespace {
35constexpr double kInf = std::numeric_limits<double>::infinity();
36
37bool HasPrimalFeasibleSolution(const SolutionProto& solution) {
38 return solution.has_primal_solution() &&
39 solution.primal_solution().feasibility_status() ==
40 SOLUTION_STATUS_FEASIBLE;
41}
42
43bool HasPrimalFeasibleSolution(
44 const google::protobuf::RepeatedPtrField<SolutionProto>& solutions) {
45 // Assumes first solution is primal feasible if there is any primal solution.
46 return !solutions.empty() && HasPrimalFeasibleSolution(solutions.at(0));
47}
48
49bool HasDualFeasibleSolution(const SolutionProto& solution) {
50 return solution.has_dual_solution() &&
51 solution.dual_solution().feasibility_status() ==
52 SOLUTION_STATUS_FEASIBLE;
53}
54
55bool HasDualFeasibleSolution(
56 const google::protobuf::RepeatedPtrField<SolutionProto>& solutions) {
57 for (const auto& solution : solutions) {
58 if (HasDualFeasibleSolution(solution)) {
59 return true;
60 }
61 }
62 return false;
63}
64} // namespace
65
66absl::Status ValidateSolutions(
67 const google::protobuf::RepeatedPtrField<SolutionProto>& solutions,
68 const ModelSolveParametersProto& parameters,
69 const ModelSummary& model_summary) {
70 // Validate individual solutions
71 for (int i = 0; i < solutions.size(); ++i) {
72 RETURN_IF_ERROR(ValidateSolution(solutions[i], parameters, model_summary))
73 << "invalid solutions[" << i << "]";
74 }
75
76 if (solutions.empty()) return absl::OkStatus();
77
78 // Validate solution order.
79 // TODO(b/204457524): check objective ordering when possible.
80 bool previous_primal_feasible = HasPrimalFeasibleSolution(solutions[0]);
81 bool previous_dual_feasible = HasDualFeasibleSolution(solutions[0]);
82 for (int i = 1; i < solutions.size(); ++i) {
83 const bool current_primal_feasible =
84 HasPrimalFeasibleSolution(solutions[i]);
85 const bool current_dual_feasible = HasDualFeasibleSolution(solutions[i]);
86 // Primal-feasible solutions must appear first.
87 if (current_primal_feasible && !previous_primal_feasible) {
88 return absl::InvalidArgumentError(
89 "primal solution ordering not satisfied");
90 }
91 // Dual-feasible solutions must appear first within the groups of
92 // primal-feasible and other solutions. Equivalently, a dual-feasible
93 // solution must be preceded by a dual-feasible solution, except when we
94 // switch from the group of primal-feasible solutions to the group of other
95 // solutions.
96 if (current_dual_feasible && !previous_dual_feasible) {
97 if (!(previous_primal_feasible && !current_primal_feasible)) {
98 return absl::InvalidArgumentError(
99 "dual solution ordering not satisfied");
100 }
101 }
102 previous_primal_feasible = current_primal_feasible;
103 previous_dual_feasible = current_dual_feasible;
104 }
105 return absl::OkStatus();
106}
107
108namespace {
109absl::Status RequireNoPrimalFeasibleSolution(const SolveResultProto& result) {
110 if (HasPrimalFeasibleSolution(result.solutions())) {
111 return absl::InvalidArgumentError(
112 "expected no primal feasible solution, but one was returned");
113 }
114
115 return absl::OkStatus();
116}
117
118bool FirstPrimalObjectiveIsStrictlyBetter(const double first_objective,
119 const double second_objective,
120 const bool maximize) {
121 if (maximize) {
122 return first_objective > second_objective;
123 }
124 return first_objective < second_objective;
125}
126
127bool FirstDualObjectiveIsStrictlyBetter(const double first_objective,
128 const double second_objective,
129 const bool maximize) {
130 if (maximize) {
131 return second_objective > first_objective;
132 }
133 return second_objective < first_objective;
134}
135
136double GetBestPrimalObjective(
137 const google::protobuf::RepeatedPtrField<SolutionProto>& solutions,
138 const bool maximize) {
139 double best_objective = maximize ? -kInf : kInf;
140 for (int i = 0; i < solutions.size(); ++i) {
141 if (HasPrimalFeasibleSolution(solutions[i])) {
142 const double primal_objective =
143 solutions[i].primal_solution().objective_value();
144 if (FirstPrimalObjectiveIsStrictlyBetter(primal_objective, best_objective,
145 maximize)) {
146 best_objective = primal_objective;
147 }
148 }
149 }
150 return best_objective;
151}
152
153double GetBestDualObjective(
154 const google::protobuf::RepeatedPtrField<SolutionProto>& solutions,
155 const bool maximize) {
156 double best_objective = maximize ? kInf : -kInf;
157 for (int i = 0; i < solutions.size(); ++i) {
158 if (HasDualFeasibleSolution(solutions[i])) {
159 const DualSolutionProto& dual_solution = solutions[i].dual_solution();
160 if (dual_solution.has_objective_value() &&
161 FirstDualObjectiveIsStrictlyBetter(dual_solution.objective_value(),
162 best_objective, maximize)) {
163 best_objective = dual_solution.objective_value();
164 }
165 }
166 }
167 return best_objective;
168}
169} // namespace
170
171absl::Status CheckHasPrimalSolution(const SolveResultProto& result) {
172 if (!HasPrimalFeasibleSolution(result.solutions())) {
173 return absl::InvalidArgumentError(
174 "primal feasible solution expected, but not found");
175 }
176
177 return absl::OkStatus();
178}
179
181 const TerminationProto& termination,
182 const google::protobuf::RepeatedPtrField<SolutionProto>& solutions,
183 const bool maximize) {
184 if (!HasPrimalFeasibleSolution(solutions)) {
185 return absl::OkStatus();
186 }
187 if (termination.problem_status().primal_status() !=
188 FEASIBILITY_STATUS_FEASIBLE) {
189 return absl::InvalidArgumentError(
190 "primal feasibility status is not FEASIBILITY_STATUS_FEASIBLE, but "
191 "primal feasible solution is returned.");
192 }
193 const double best_objective = GetBestPrimalObjective(solutions, maximize);
194 const double primal_bound = termination.objective_bounds().primal_bound();
195 if (FirstPrimalObjectiveIsStrictlyBetter(best_objective, primal_bound,
196 maximize)) {
198 << "best primal feasible solution objective = " << best_objective
199 << " is better than primal_bound = " << primal_bound;
200 }
201 return absl::OkStatus();
202}
203
205 const TerminationProto& termination,
206 const google::protobuf::RepeatedPtrField<SolutionProto>& solutions,
207 const bool maximize) {
208 if (termination.problem_status().dual_status() !=
209 FEASIBILITY_STATUS_FEASIBLE &&
210 HasDualFeasibleSolution(solutions)) {
211 return absl::InvalidArgumentError(
212 "dual feasibility status is not FEASIBILITY_STATUS_FEASIBLE, but "
213 "dual feasible solution is returned.");
214 }
215 const double best_objective = GetBestDualObjective(solutions, maximize);
216 const double dual_bound = termination.objective_bounds().dual_bound();
217 if (FirstDualObjectiveIsStrictlyBetter(best_objective, dual_bound,
218 maximize)) {
220 << "best dual feasible solution objective = " << best_objective
221 << " is better than dual_bound = " << dual_bound;
222 }
223 return absl::OkStatus();
224}
225
226namespace {
227// TODO(b/290091715): Delete once problem_status and objective bounds are
228// removed from solve_stats and their presence is guaranteed in termination.
229absl::Status ValidateSolveStatsTerminationEqualities(
230 const SolveResultProto& solve_result) {
231 const ObjectiveBoundsProto objective_bounds =
232 GetObjectiveBounds(solve_result);
233 const SolveStatsProto& solve_stats = solve_result.solve_stats();
234 const ProblemStatusProto problem_status = GetProblemStatus(solve_result);
235 if (problem_status.primal_status() !=
236 solve_stats.problem_status().primal_status()) {
238 << problem_status.primal_status()
239 << " = termination.problem_status.primal_status != "
240 "solve_stats.problem_status.primal_status = "
241 << solve_stats.problem_status().primal_status();
242 }
243 if (problem_status.dual_status() !=
244 solve_stats.problem_status().dual_status()) {
246 << problem_status.dual_status()
247 << " = termination.problem_status.dual_status != "
248 "solve_stats.problem_status.dual_status = "
249 << solve_stats.problem_status().dual_status();
250 }
251 if (problem_status.primal_or_dual_infeasible() !=
252 solve_stats.problem_status().primal_or_dual_infeasible()) {
254 << problem_status.primal_or_dual_infeasible()
255 << " = termination.problem_status.primal_or_dual_infeasible != "
256 "solve_stats.problem_status.primal_or_dual_infeasible = "
257 << solve_stats.problem_status().primal_or_dual_infeasible();
258 }
259 if (objective_bounds.primal_bound() != solve_stats.best_primal_bound()) {
261 << objective_bounds.primal_bound()
262 << " = termination.objective_bounds.primal_bound != "
263 "solve_stats.best_primal_bound = "
264 << solve_stats.best_primal_bound();
265 }
266 if (objective_bounds.dual_bound() != solve_stats.best_dual_bound()) {
268 << objective_bounds.dual_bound()
269 << " = termination.objective_bounds.dual_bound != "
270 "solve_stats.best_dual_bound = "
271 << solve_stats.best_dual_bound();
272 }
273 return absl::OkStatus();
274}
275
276} // namespace
277
278absl::Status ValidateResult(const SolveResultProto& result,
279 const ModelSolveParametersProto& parameters,
280 const ModelSummary& model_summary) {
281 // TODO(b/290091715): Remove once problem_status and objective bounds are
282 // removed from solve_stats and their presence is guaranteed in termination.
283 RETURN_IF_ERROR(ValidateSolveStatsTerminationEqualities(result));
284 // TODO(b/290091715): Replace by
285 // TerminationProto termination = result.termination();
286 // once problem_status and objective bounds are removed from solve_stats and
287 // their presence is guaranteed in termination.
288 TerminationProto termination = result.termination();
289 *termination.mutable_objective_bounds() = GetObjectiveBounds(result);
290 *termination.mutable_problem_status() = GetProblemStatus(result);
291
293 RETURN_IF_ERROR(ValidateSolveStats(result.solve_stats()));
295 ValidateSolutions(result.solutions(), parameters, model_summary));
296
297 if (termination.reason() == TERMINATION_REASON_OPTIMAL ||
298 termination.reason() == TERMINATION_REASON_FEASIBLE) {
300 << "inconsistent termination reason "
301 << ProtoEnumToString(termination.reason());
302 }
303 if (termination.reason() == TERMINATION_REASON_NO_SOLUTION_FOUND) {
304 RETURN_IF_ERROR(RequireNoPrimalFeasibleSolution(result))
305 << "inconsistent termination reason "
306 << ProtoEnumToString(termination.reason());
307 }
308
310 result.termination(), result.solutions(), model_summary.maximize));
312 result.termination(), result.solutions(), model_summary.maximize));
313
314 if (result.primal_rays_size() > 0 &&
315 termination.problem_status().dual_status() ==
316 FEASIBILITY_STATUS_FEASIBLE) {
317 return absl::InvalidArgumentError(
318 "termination.problem_status.dual_status = "
319 "FEASIBILITY_STATUS_FEASIBLE, "
320 "but a primal ray is returned");
321 }
322 for (int i = 0; i < result.primal_rays_size(); ++i) {
323 RETURN_IF_ERROR(ValidatePrimalRay(result.primal_rays(i),
324 parameters.variable_values_filter(),
325 model_summary))
326 << "Invalid primal_rays[" << i << "]";
327 }
328 if (result.dual_rays_size() > 0 &&
329 termination.problem_status().primal_status() ==
330 FEASIBILITY_STATUS_FEASIBLE) {
331 return absl::InvalidArgumentError(
332 "termination.problem_status.primal_status = "
333 "FEASIBILITY_STATUS_FEASIBLE, but a dual ray is returned");
334 }
335 for (int i = 0; i < result.dual_rays_size(); ++i) {
337 ValidateDualRay(result.dual_rays(i), parameters, model_summary))
338 << "Invalid dual_rays[" << i << "]";
339 }
340
341 return absl::OkStatus();
342}
343
344} // namespace math_opt
345} // namespace operations_research
#define RETURN_IF_ERROR(expr)
SatParameters parameters
TerminationReason termination
double solution
absl::Status ValidateDualRay(const DualRayProto &dual_ray, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status CheckDualSolutionAndStatusConsistency(const TerminationProto &termination, const google::protobuf::RepeatedPtrField< SolutionProto > &solutions, const bool maximize)
ProblemStatusProto GetProblemStatus(const SolveResultProto &solve_result)
ObjectiveBoundsProto GetObjectiveBounds(const SolveResultProto &solve_result)
absl::Status ValidateSolveStats(const SolveStatsProto &solve_stats)
absl::Status CheckHasPrimalSolution(const SolveResultProto &result)
Returns absl::Ok only if a primal feasible solution is available.
absl::Status ValidateSolution(const SolutionProto &solution, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status CheckPrimalSolutionAndTerminationConsistency(const TerminationProto &termination, const google::protobuf::RepeatedPtrField< SolutionProto > &solutions, const bool maximize)
absl::Status ValidateResult(const SolveResultProto &result, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
Validates the input result.
absl::Status ValidateTermination(const TerminationProto &termination, const bool is_maximize)
Checks all messages are valid and compatible.
absl::Status ValidateSolutions(const google::protobuf::RepeatedPtrField< SolutionProto > &solutions, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status ValidatePrimalRay(const PrimalRayProto &primal_ray, const SparseVectorFilterProto &filter, const ModelSummary &model_summary)
In SWIG mode, we don't want anything besides these top-level includes.
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:50
StatusBuilder InvalidArgumentErrorBuilder()