OR-Tools  9.6
solution_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 <cstdint>
17 #include <limits>
18 #include <string>
19 
20 #include "absl/status/status.h"
21 #include "absl/strings/str_cat.h"
27 #include "ortools/math_opt/model_parameters.pb.h"
28 #include "ortools/math_opt/solution.pb.h"
29 #include "ortools/math_opt/sparse_containers.pb.h"
33 
34 namespace operations_research {
35 namespace math_opt {
36 namespace {
37 
38 absl::Status ValidateSolutionStatus(const SolutionStatusProto& status) {
39  if (!SolutionStatusProto_IsValid(status)) {
40  return absl::InvalidArgumentError(absl::StrCat("status = ", status));
41  }
42  if (status == SOLUTION_STATUS_UNSPECIFIED) {
43  return absl::InvalidArgumentError("status = SOLUTION_STATUS_UNSPECIFIED");
44  }
45  return absl::OkStatus();
46 }
47 
48 } // namespace
49 
51 // Solutions & Rays
53 
54 namespace {
55 
56 // Validates that all pairs in the input view match the provided filter and that
57 // all expected values are there when skip_zero_values is not used.
58 //
59 // This function assumes caller have checked already that the input
60 // vector_view.ids() and the input filter are valid.
61 template <typename T>
62 absl::Status IsFiltered(const SparseVectorView<T>& vector_view,
63  const SparseVectorFilterProto& filter,
64  const IdNameBiMap& all_items) {
66  SparseVectorFilterPredicate predicate(filter);
67  RETURN_IF_ERROR(CheckIdsSubset(vector_view.ids(), all_items, "sparse vector",
68  "model IDs"));
69  for (int i = 0; i < vector_view.ids_size(); ++i) {
70  const int64_t id = vector_view.ids(i);
71  if (!predicate.AcceptsAndUpdate(id, vector_view.values(i))) {
72  return absl::InvalidArgumentError(
73  absl::StrCat("sparse vector should not contain the pair (id: ", id,
74  ", value: ", vector_view.values(i), ") (at index: ", i,
75  ") that should have been filtered"));
76  }
77  }
78 
79  // We don't test the length on the input if we skipped the zeros since missing
80  // values are expected.
81  if (filter.skip_zero_values()) {
82  return absl::OkStatus();
83  }
84 
85  const int expected_size =
86  filter.filter_by_ids() ? filter.filtered_ids_size() : all_items.Size();
87  if (vector_view.ids_size() != expected_size) {
88  return absl::InvalidArgumentError(absl::StrCat(
89  "sparse vector should contain ", expected_size, " values but contains ",
90  vector_view.ids_size(), " instead"));
91  }
92 
93  return absl::OkStatus();
94 }
95 
96 // A solution vector is valid if:
97 // * it is a valid SparseDoubleVectorProto.
98 // * its values are finite.
99 // * it contains only elements that pass the filter
100 // * it contains all elements that pass the filter when skip_zero_values is not
101 // used.
102 //
103 // TODO(b/174345677): check that the ids are valid.
104 absl::Status IsValidSolutionVector(const SparseDoubleVectorProto& vector,
105  const SparseVectorFilterProto& filter,
106  const IdNameBiMap& all_items) {
107  const auto vector_view = MakeView(vector);
109  vector_view,
110  {.allow_positive_infinity = false, .allow_negative_infinity = false}));
111  RETURN_IF_ERROR(IsFiltered(vector_view, filter, all_items));
112  return absl::OkStatus();
113 }
114 
115 } // namespace
116 
117 absl::Status ValidateSolution(const SolutionProto& solution,
118  const ModelSolveParametersProto& parameters,
119  const ModelSummary& model_summary) {
120  if (!solution.has_primal_solution() && !solution.has_dual_solution() &&
121  !solution.has_basis()) {
122  return absl::InvalidArgumentError("empty solution");
123  }
124  if (solution.has_primal_solution()) {
125  RETURN_IF_ERROR(ValidatePrimalSolution(solution.primal_solution(),
126  parameters.variable_values_filter(),
127  model_summary))
128  << "invalid primal_solution";
129  }
130  if (solution.has_dual_solution()) {
131  RETURN_IF_ERROR(ValidateDualSolution(solution.dual_solution(), parameters,
132  model_summary))
133  << "invalid dual_solution";
134  }
135  if (solution.has_basis()) {
136  RETURN_IF_ERROR(ValidateBasis(solution.basis(), model_summary))
137  << "invalid basis";
138  }
139  // TODO(b/204457524): consider checking equality of statuses for single-sided
140  // LPs.
141  if (solution.has_basis() && solution.has_dual_solution()) {
142  if (solution.basis().basic_dual_feasibility() == SOLUTION_STATUS_FEASIBLE &&
143  solution.dual_solution().feasibility_status() !=
144  SOLUTION_STATUS_FEASIBLE) {
145  return absl::InvalidArgumentError(
146  "incompatible basis and dual solution: basis is dual feasible, but "
147  "dual solution is not feasible");
148  }
149  if (solution.dual_solution().feasibility_status() ==
150  SOLUTION_STATUS_INFEASIBLE &&
151  solution.basis().basic_dual_feasibility() !=
152  SOLUTION_STATUS_INFEASIBLE) {
153  return absl::InvalidArgumentError(
154  "incompatible basis and dual solution: dual solution is infeasible, "
155  "but basis is not dual infeasible");
156  }
157  }
158  return absl::OkStatus();
159 }
160 
161 absl::Status ValidatePrimalSolutionVector(const SparseDoubleVectorProto& vector,
162  const SparseVectorFilterProto& filter,
163  const ModelSummary& model_summary) {
165  IsValidSolutionVector(vector, filter, model_summary.variables));
166  return absl::OkStatus();
167 }
168 
169 absl::Status ValidatePrimalSolution(const PrimalSolutionProto& primal_solution,
170  const SparseVectorFilterProto& filter,
171  const ModelSummary& model_summary) {
172  RETURN_IF_ERROR(ValidateSolutionStatus(primal_solution.feasibility_status()))
173  << "invalid PrimalSolutionProto.feasibility_status";
175  primal_solution.variable_values(), filter, model_summary))
176  << "invalid PrimalSolutionProto.variable_values";
177  RETURN_IF_ERROR(CheckScalarNoNanNoInf(primal_solution.objective_value()))
178  << "invalid PrimalSolutionProto.objective_value";
179  for (const auto [id, obj_value] :
180  primal_solution.auxiliary_objective_values()) {
181  if (!model_summary.auxiliary_objectives.HasId(id)) {
183  << "unrecognized auxiliary objective ID: " << id
184  << "; invalid PrimalSolutionProto.auxiliary_objective_values";
185  }
187  << "invalid PrimalSolutionProto.auxiliary_objective_values";
188  }
189  return absl::OkStatus();
190 }
191 
192 absl::Status ValidatePrimalRay(const PrimalRayProto& primal_ray,
193  const SparseVectorFilterProto& filter,
194  const ModelSummary& model_summary) {
195  RETURN_IF_ERROR(IsValidSolutionVector(primal_ray.variable_values(), filter,
196  model_summary.variables))
197  << "invalid PrimalRayProto.variable_values";
198  return absl::OkStatus();
199 }
200 
201 absl::Status ValidateDualSolution(const DualSolutionProto& dual_solution,
202  const ModelSolveParametersProto& parameters,
203  const ModelSummary& model_summary) {
204  RETURN_IF_ERROR(ValidateSolutionStatus(dual_solution.feasibility_status()))
205  << "invalid DualSolutionProto.feasibility_status";
206  RETURN_IF_ERROR(IsValidSolutionVector(dual_solution.dual_values(),
207  parameters.dual_values_filter(),
208  model_summary.linear_constraints))
209  << "invalid DualSolutionProto.dual_values";
210  RETURN_IF_ERROR(IsValidSolutionVector(dual_solution.reduced_costs(),
211  parameters.reduced_costs_filter(),
212  model_summary.variables))
213  << "invalid DualSolutionProto.reduced_costs";
214  RETURN_IF_ERROR(CheckScalarNoNanNoInf(dual_solution.objective_value()))
215  << "invalid DualSolutionProto.objective_value";
216  return absl::OkStatus();
217 }
218 
219 absl::Status ValidateDualRay(const DualRayProto& dual_ray,
220  const ModelSolveParametersProto& parameters,
221  const ModelSummary& model_summary) {
222  RETURN_IF_ERROR(IsValidSolutionVector(dual_ray.dual_values(),
223  parameters.dual_values_filter(),
224  model_summary.linear_constraints))
225  << "invalid DualRayProto.dual_values";
226  RETURN_IF_ERROR(IsValidSolutionVector(dual_ray.reduced_costs(),
227  parameters.reduced_costs_filter(),
228  model_summary.variables))
229  << "invalid DualRayProto.reduced_costs";
230  return absl::OkStatus();
231 }
232 
234 // Basis
236 
238  const SparseVectorView<int>& status_vector_view) {
239  RETURN_IF_ERROR(CheckIdsAndValues(status_vector_view));
240  for (auto [id, value] : status_vector_view) {
241  if (!BasisStatusProto_IsValid(value)) {
242  return absl::InvalidArgumentError(
243  absl::StrCat("invalid status: ", value, " for id ", id));
244  }
245  if (value == BASIS_STATUS_UNSPECIFIED) {
246  return absl::InvalidArgumentError(
247  absl::StrCat("found BASIS_STATUS_UNSPECIFIED for id ", id));
248  }
249  }
250  return absl::OkStatus();
251 }
252 
253 absl::Status ValidateBasis(const BasisProto& basis,
254  const ModelSummary& model_summary,
255  const bool check_dual_feasibility) {
256  if (check_dual_feasibility) {
257  RETURN_IF_ERROR(ValidateSolutionStatus(basis.basic_dual_feasibility()))
258  << "invalid BasisProto.basic_dual_feasibility";
259  }
260  const auto constraint_status_view = MakeView(basis.constraint_status());
261  const auto variable_status_view = MakeView(basis.variable_status());
262  RETURN_IF_ERROR(SparseBasisStatusVectorIsValid(constraint_status_view))
263  << absl::StrCat("BasisProto.constraint_status invalid");
264  RETURN_IF_ERROR(SparseBasisStatusVectorIsValid(variable_status_view))
265  << absl::StrCat("BasisProto.variable_status invalid");
266 
268  basis.constraint_status().ids(), model_summary.linear_constraints,
269  "BasisProto.constraint_status.ids", "model_summary.linear_constraints"));
271  basis.variable_status().ids(), model_summary.variables,
272  "BasisProto.variable_status.ids", "model_summary.variables"));
273 
274  int non_basic_variables = 0;
275  for (const auto [id, value] : constraint_status_view) {
276  if (value != BASIS_STATUS_BASIC) {
277  non_basic_variables++;
278  }
279  }
280  for (auto [id, value] : variable_status_view) {
281  if (value != BASIS_STATUS_BASIC) {
282  non_basic_variables++;
283  }
284  }
285  if (non_basic_variables != model_summary.variables.Size()) {
286  return absl::InvalidArgumentError(absl::StrCat(
287  "inconsistent number of non-basic variable+constraints: ",
288  non_basic_variables, ", variables: ", model_summary.variables.Size()));
289  }
290  return absl::OkStatus();
291 }
292 
293 } // namespace math_opt
294 } // namespace operations_research
#define RETURN_IF_ERROR(expr)
SatParameters parameters
int64_t value
absl::Status status
Definition: g_gurobi.cc:41
absl::Status CheckIdsAndValuesSize(const SparseVectorView< T > &vector_view, absl::string_view value_name="values")
absl::Status ValidateBasis(const BasisProto &basis, const ModelSummary &model_summary, const bool check_dual_feasibility)
absl::Status SparseBasisStatusVectorIsValid(const SparseVectorView< int > &status_vector_view)
absl::Status ValidatePrimalSolution(const PrimalSolutionProto &primal_solution, const SparseVectorFilterProto &filter, const ModelSummary &model_summary)
absl::Status ValidatePrimalSolutionVector(const SparseDoubleVectorProto &vector, const SparseVectorFilterProto &filter, const ModelSummary &model_summary)
absl::Status CheckIdsSubset(absl::Span< const int64_t > ids, const IdNameBiMap &universe, std::optional< int64_t > upper_bound)
absl::Status CheckIdsAndValues(const SparseVectorView< T > &vector_view, absl::string_view value_name="values")
absl::Status ValidatePrimalRay(const PrimalRayProto &primal_ray, const SparseVectorFilterProto &filter, const ModelSummary &model_summary)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
absl::Status ValidateDualRay(const DualRayProto &dual_ray, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status CheckScalarNoNanNoInf(const double d)
absl::Status ValidateSolution(const SolutionProto &solution, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status ValidateDualSolution(const DualSolutionProto &dual_solution, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status CheckIdsIdentical(absl::Span< const int64_t > first_ids, const IdNameBiMap &second_ids, absl::string_view first_description, absl::string_view second_description)
Collection of objects used to extend the Constraint Solver library.
StatusBuilder InvalidArgumentErrorBuilder()