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