Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
callback_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 <algorithm>
17#include <limits>
18#include <string>
19#include <vector>
20
21#include "absl/container/flat_hash_set.h"
22#include "absl/status/status.h"
23#include "absl/strings/str_cat.h"
24#include "absl/strings/str_join.h"
25#include "google/protobuf/duration.pb.h"
28#include "ortools/math_opt/callback.pb.h"
32#include "ortools/math_opt/solution.pb.h"
33#include "ortools/math_opt/sparse_containers.pb.h"
40
41namespace operations_research {
42namespace math_opt {
43namespace {
44
45constexpr double kInf = std::numeric_limits<double>::infinity();
46
47absl::Status IsEventRegistered(
48 const CallbackEventProto event,
49 const CallbackRegistrationProto& callback_registration) {
50 // Unfortunately the range iterator return ints and not CallbackEventProtos.
51 const int num_events = callback_registration.request_registration_size();
52 for (int k = 0; k < num_events; ++k) {
53 if (callback_registration.request_registration(k) == event) {
54 return absl::OkStatus();
55 }
56 }
57 return absl::InvalidArgumentError(absl::StrCat(
58 "event ", ProtoEnumToString(event),
59 " not part of the registered_events in callback_registration"));
60}
61
62absl::Status ValidateGeneratedLinearConstraint(
63 const CallbackResultProto::GeneratedLinearConstraint& linear_constraint,
64 const bool add_cuts, const bool add_lazy_constraints,
65 const ModelSummary& model_summary) {
66 const auto coefficients = MakeView(linear_constraint.linear_expression());
69 {.allow_positive_infinity = false, .allow_negative_infinity = false}))
70 << "invalid linear_constraint coefficients";
71 RETURN_IF_ERROR(CheckIdsSubset(coefficients.ids(), model_summary.variables,
72 "cut variables", "model IDs"));
73 RETURN_IF_ERROR(CheckScalar(linear_constraint.lower_bound(),
74 {.allow_positive_infinity = false}))
75 << "for GeneratedLinearConstraint.lower_bound";
76 RETURN_IF_ERROR(CheckScalar(linear_constraint.upper_bound(),
77 {.allow_negative_infinity = false}))
78 << "for GeneratedLinearConstraint.upper_bound";
79 if (linear_constraint.lower_bound() == -kInf &&
80 linear_constraint.upper_bound() == kInf) {
81 return absl::InvalidArgumentError(
82 "invalid GeneratedLinearConstraint, bounds [-inf,inf]");
83 }
84 if (linear_constraint.is_lazy() && !add_lazy_constraints) {
85 return absl::InvalidArgumentError(
86 "invalid GeneratedLinearConstraint with lazy attribute set to true, "
87 "adding lazy constraints requires "
88 "CallbackRegistrationProto.add_lazy_constraints=true");
89 }
90 if (!linear_constraint.is_lazy() && !add_cuts) {
91 return absl::InvalidArgumentError(
92 "invalid GeneratedLinearConstraint with lazy attribute set to false, "
93 "adding cuts requires CallbackRegistrationProto.add_cuts=true");
94 }
95 return absl::OkStatus();
96}
97
98template <typename T>
99struct ProtoEnumFormatter {
100 void operator()(std::string* const out, const T value) {
101 out->append(ProtoEnumToString(value));
102 }
103};
104
105} // namespace
106
108 const CallbackRegistrationProto& callback_registration,
109 const ModelSummary& model_summary) {
111 callback_registration.mip_solution_filter(), model_summary.variables))
112 << "invalid CallbackRegistrationProto.mip_solution_filter";
114 callback_registration.mip_node_filter(), model_summary.variables))
115 << "invalid CallbackRegistrationProto.mip_node_filter";
116 // Unfortunately the range iterator return ints and not CallbackEventProtos.
117 const int num_events = callback_registration.request_registration_size();
118 bool can_add_lazy_constraints = false;
119 bool can_add_cuts = false;
120 for (int k = 0; k < num_events; ++k) {
121 const CallbackEventProto requested_event =
122 callback_registration.request_registration(k);
123 if (requested_event == CALLBACK_EVENT_UNSPECIFIED ||
124 !CallbackEventProto_IsValid(requested_event)) {
125 return absl::InvalidArgumentError(absl::StrCat(
126 "invalid event ", requested_event, " can not be registered"));
127 }
128 if (requested_event == CALLBACK_EVENT_MIP_NODE) {
129 can_add_lazy_constraints = true;
130 can_add_cuts = true;
131 }
132 if (requested_event == CALLBACK_EVENT_MIP_SOLUTION) {
133 can_add_lazy_constraints = true;
134 }
135 }
136 if (callback_registration.add_cuts() && !can_add_cuts) {
137 return absl::InvalidArgumentError(
138 "can only add cuts at event CALLBACK_EVENT_MIP_NODE but this event was "
139 "not requested");
140 }
141 if (callback_registration.add_lazy_constraints() &&
142 !can_add_lazy_constraints) {
143 return absl::InvalidArgumentError(
144 "can only add lazy constraints at events CALLBACK_EVENT_MIP_NODE and "
145 "CALLBACK_EVENT_MIP_SOLUTION but neither of these events were "
146 "requested");
147 }
148
149 return absl::OkStatus();
150}
151
153 const CallbackDataProto& cb_data,
154 const CallbackRegistrationProto& callback_registration,
155 const ModelSummary& model_summary) {
156 const CallbackEventProto event = cb_data.event();
157 RETURN_IF_ERROR(IsEventRegistered(event, callback_registration))
158 << "invalid CallbackDataProto.event for given CallbackRegistrationProto";
159
160 const bool has_primal_solution = cb_data.has_primal_solution_vector();
161 if (has_primal_solution && event != CALLBACK_EVENT_MIP_SOLUTION &&
162 event != CALLBACK_EVENT_MIP_NODE) {
163 return absl::InvalidArgumentError(
164 absl::StrCat("can't provide primal_solution_vector for event ", event,
165 " (", ProtoEnumToString(event), ")"));
166 }
167
168#ifdef RETURN_IF_SCALAR
169#error Collision in macro definition RETURN_IF_SCALAR
170#endif
171#define RETURN_IF_SCALAR(stat, value, option) \
172 do { \
173 if (stat.has_##value()) { \
174 RETURN_IF_ERROR(CheckScalar(static_cast<double>(stat.value()), option)) \
175 << "Invalid CallbackDataProto." << #stat << "." << #value; \
176 } \
177 } while (false)
178 const DoubleOptions nonan;
179 const DoubleOptions finite = {.allow_positive_infinity = false,
180 .allow_negative_infinity = false};
181 const DoubleOptions noneg = {.allow_positive_infinity = false,
182 .allow_negative = false};
183 // Check PresolveStats.
184 const auto& presolve_stats = cb_data.presolve_stats();
185 RETURN_IF_SCALAR(presolve_stats, bound_changes, noneg);
186 RETURN_IF_SCALAR(presolve_stats, coefficient_changes, noneg);
187
188 // Check SimplexStats.
189 const auto& simplex_stats = cb_data.simplex_stats();
190 RETURN_IF_SCALAR(simplex_stats, iteration_count, noneg);
191 RETURN_IF_SCALAR(simplex_stats, objective_value, finite);
192 RETURN_IF_SCALAR(simplex_stats, primal_infeasibility, noneg);
193 RETURN_IF_SCALAR(simplex_stats, dual_infeasibility, noneg);
194
195 // Check BarrierStats.
196 const auto& barrier_stats = cb_data.barrier_stats();
197 RETURN_IF_SCALAR(barrier_stats, iteration_count, noneg);
198 RETURN_IF_SCALAR(barrier_stats, primal_objective, finite);
199 RETURN_IF_SCALAR(barrier_stats, dual_objective, finite);
200 RETURN_IF_SCALAR(barrier_stats, complementarity, finite);
201 RETURN_IF_SCALAR(barrier_stats, primal_infeasibility, noneg);
202 RETURN_IF_SCALAR(barrier_stats, dual_infeasibility, noneg);
203
204 // Check MipStats.
205 const auto& mip_stats = cb_data.mip_stats();
206 RETURN_IF_SCALAR(mip_stats, primal_bound, nonan);
207 RETURN_IF_SCALAR(mip_stats, dual_bound, nonan);
208 RETURN_IF_SCALAR(mip_stats, explored_nodes, noneg);
209 RETURN_IF_SCALAR(mip_stats, open_nodes, noneg);
210 RETURN_IF_SCALAR(mip_stats, simplex_iterations, noneg);
211 RETURN_IF_SCALAR(mip_stats, number_of_solutions_found, noneg);
212 RETURN_IF_SCALAR(mip_stats, cutting_planes_in_lp, noneg);
213
214 // Check runtime.
215 RETURN_IF_ERROR(CheckScalar(cb_data.runtime().seconds(), noneg))
216 << "Invalid CallbackDataProto.runtime.seconds";
217 RETURN_IF_ERROR(CheckScalar(cb_data.runtime().nanos(), noneg))
218 << "Invalid CallbackDataProto.runtime.nanos";
219#undef RETURN_IF_SCALAR
220
221 // Ensure required fields are available depending on event.
222 switch (event) {
223 case CALLBACK_EVENT_MIP_NODE:
224 case CALLBACK_EVENT_MIP_SOLUTION: {
225 if (has_primal_solution) {
226 const SparseVectorFilterProto& filter =
227 event == CALLBACK_EVENT_MIP_NODE
228 ? callback_registration.mip_node_filter()
229 : callback_registration.mip_solution_filter();
231 cb_data.primal_solution_vector(), filter, model_summary))
232 << "invalid CallbackDataProto.primal_solution_vector";
233 } else if (event == CALLBACK_EVENT_MIP_SOLUTION) {
234 return absl::InvalidArgumentError(
235 absl::StrCat("must provide primal_solution_vector for event ",
236 event, " (", ProtoEnumToString(event), ")"));
237 }
238 break;
239 }
240
241 case CALLBACK_EVENT_UNSPECIFIED:
242 // This can not happen as a valid callback_registration can not register
243 // a CALLBACK_EVENT_UNSPECIFIED.
244 LOG(FATAL)
245 << "CALLBACK_EVENT_UNSPECIFIED can not be a registered event, this "
246 "points to either an invalid CallbackRegistrationProto (which "
247 "violates "
248 "one of the assumptions of this function), or memory corruption";
249 default:
250 // The remaining events are just for information collection. No further
251 // test required.
252 break;
253 }
254
255 return absl::OkStatus();
256}
257
259 const CallbackResultProto& callback_result,
260 const CallbackEventProto callback_event,
261 const CallbackRegistrationProto& callback_registration,
262 const ModelSummary& model_summary) {
263 // We assume that all arguments but the first are valid and concordant with
264 // each other. Otherwise this is an internal implementation error.
265 CHECK_OK(IsEventRegistered(callback_event, callback_registration));
266
267 if (!callback_result.cuts().empty()) {
268 if (callback_event != CALLBACK_EVENT_MIP_NODE &&
269 callback_event != CALLBACK_EVENT_MIP_SOLUTION) {
270 return absl::InvalidArgumentError(absl::StrCat(
271 "invalid CallbackResultProto, can't return cuts for callback_event ",
272 callback_event, "(", ProtoEnumToString(callback_event), ")"));
273 }
274 for (const CallbackResultProto::GeneratedLinearConstraint& cut :
275 callback_result.cuts()) {
276 RETURN_IF_ERROR(ValidateGeneratedLinearConstraint(
277 cut, callback_registration.add_cuts(),
278 callback_registration.add_lazy_constraints(), model_summary));
279 }
280 }
281 if (!callback_result.suggested_solutions().empty()) {
282 if (callback_event != CALLBACK_EVENT_MIP_NODE) {
283 return absl::InvalidArgumentError(absl::StrCat(
284 "invalid CallbackResultProto, can't return suggested solutions for "
285 "callback_event ",
286 callback_event, "(", ProtoEnumToString(callback_event), ")"));
287 }
288 for (const SparseDoubleVectorProto& primal_solution_vector :
289 callback_result.suggested_solutions()) {
291 primal_solution_vector, SparseVectorFilterProto(), model_summary))
292 << "invalid CallbackResultProto.suggested_solutions";
293 }
294 }
295
296 return absl::OkStatus();
297}
298
300 const CallbackRegistrationProto& registration,
301 const absl::flat_hash_set<CallbackEventProto>& supported_events) {
302 std::vector<CallbackEventProto> unsupported_events;
303 for (const CallbackEventProto event : EventSet(registration)) {
304 if (!supported_events.contains(event)) {
305 unsupported_events.push_back(event);
306 }
307 }
308
309 if (unsupported_events.empty()) {
310 return absl::OkStatus();
311 }
312
313 std::sort(unsupported_events.begin(), unsupported_events.end());
314
315 const bool plural = unsupported_events.size() >= 2;
316 return absl::InvalidArgumentError(
317 absl::StrCat("event", (plural ? "s { " : " "),
318 absl::StrJoin(unsupported_events, ", ",
319 ProtoEnumFormatter<CallbackEventProto>()),
320 (plural ? " } are" : " is"), " not supported"));
321}
322
323} // namespace math_opt
324} // namespace operations_research
#define RETURN_IF_ERROR(expr)
#define RETURN_IF_SCALAR(stat, value, option)
int64_t value
absl::Span< const double > coefficients
absl::Status ValidateCallbackResultProto(const CallbackResultProto &callback_result, const CallbackEventProto callback_event, const CallbackRegistrationProto &callback_registration, const ModelSummary &model_summary)
*If primal *dual status is and *dual bound is equal to primal bound *is finite
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
Returns the callback_registration.request_registration as a set of enums.
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
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 CheckScalar(const double value, const DoubleOptions &options)
Checks value is not NaN and satisfies the additional conditions in options.
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 ValidateCallbackDataProto(const CallbackDataProto &cb_data, const CallbackRegistrationProto &callback_registration, const ModelSummary &model_summary)
absl::Status ValidateSparseVectorFilter(const SparseVectorFilterProto &v, const IdNameBiMap &valid_ids)
absl::Status ValidateCallbackRegistration(const CallbackRegistrationProto &callback_registration, const ModelSummary &model_summary)
Checks that CallbackRegistrationProto is valid given a valid model summary.
In SWIG mode, we don't want anything besides these top-level includes.
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:50
double objective_value
The value objective_vector^T * (solution - center_point).