Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
solution_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 <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
34namespace operations_research {
35namespace math_opt {
36namespace {
37
38absl::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
54namespace {
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.
61template <typename T>
62absl::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.
104absl::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
117absl::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()) {
126 parameters.variable_values_filter(),
127 model_summary))
128 << "invalid primal_solution";
129 }
130 if (solution.has_dual_solution()) {
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
161absl::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
169absl::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
192absl::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
201absl::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
219absl::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
253absl::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");
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:44
double solution
absl::Status CheckIdsAndValuesSize(const SparseVectorView< T > &vector_view, absl::string_view value_name="values")
absl::Status ValidateDualRay(const DualRayProto &dual_ray, const ModelSolveParametersProto &parameters, const ModelSummary &model_summary)
absl::Status SparseBasisStatusVectorIsValid(const SparseVectorView< int > &status_vector_view)
absl::Status ValidateDualSolution(const DualSolutionProto &dual_solution, const ModelSolveParametersProto &parameters, 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 ValidateSolution(const SolutionProto &solution, const ModelSolveParametersProto &parameters, 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 CheckIdsIdentical(absl::Span< const int64_t > first_ids, const IdNameBiMap &second_ids, absl::string_view first_description, absl::string_view second_description)
first_ids and second_ids must include distinct ids.
absl::Status CheckScalarNoNanNoInf(const double d)
absl::Status ValidateBasis(const BasisProto &basis, const ModelSummary &model_summary, const bool check_dual_feasibility)
absl::Status ValidatePrimalSolution(const PrimalSolutionProto &primal_solution, const SparseVectorFilterProto &filter, 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.
StatusBuilder InvalidArgumentErrorBuilder()