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