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