OR-Tools  9.6
callback_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 <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"
26 #include "ortools/base/logging.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 
41 namespace operations_research {
42 namespace math_opt {
43 namespace {
44 
45 constexpr double kInf = std::numeric_limits<double>::infinity();
46 
47 absl::Status IsEventRegistered(
48  const CallbackEventProto event,
49  const CallbackRegistrationProto& callback_registration) {
50  // Unfortunatelly 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 
62 absl::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 
98 template <typename T>
99 struct 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  // Unfortunatelly 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 CheckRegisteredCallbackEvents(const CallbackRegistrationProto &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
absl::Status CheckScalar(const double value, const DoubleOptions &options)
absl::Status ValidateCallbackResultProto(const CallbackResultProto &callback_result, const CallbackEventProto callback_event, const CallbackRegistrationProto &callback_registration, 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 ValidateCallbackDataProto(const CallbackDataProto &cb_data, const CallbackRegistrationProto &callback_registration, const ModelSummary &model_summary)
absl::Status ValidateCallbackRegistration(const CallbackRegistrationProto &callback_registration, const ModelSummary &model_summary)
absl::Status CheckIdsAndValues(const SparseVectorView< T > &vector_view, absl::string_view value_name="values")
absl::Status ValidateSparseVectorFilter(const SparseVectorFilterProto &v, const IdNameBiMap &valid_ids)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
Collection of objects used to extend the Constraint Solver library.
std::string ProtoEnumToString(ProtoEnumType enum_value)
double objective_value