22#include "google/protobuf/duration.pb.h"
23#include "absl/container/flat_hash_set.h"
24#include "absl/status/status.h"
25#include "absl/strings/str_cat.h"
26#include "absl/strings/str_join.h"
27#include "ortools/math_opt/callback.pb.h"
31#include "ortools/math_opt/solution.pb.h"
32#include "ortools/math_opt/sparse_containers.pb.h"
39#include "absl/status/status.h"
46constexpr double kInf = std::numeric_limits<double>::infinity();
48absl::Status IsEventRegistered(
49 const CallbackEventProto event,
50 const CallbackRegistrationProto& callback_registration) {
52 const int num_events = callback_registration.request_registration_size();
53 for (
int k = 0; k < num_events; ++k) {
54 if (callback_registration.request_registration(k) == event) {
55 return absl::OkStatus();
58 return absl::InvalidArgumentError(absl::StrCat(
60 " not part of the registered_events in callback_registration"));
63absl::Status ValidateGeneratedLinearConstraint(
64 const CallbackResultProto::GeneratedLinearConstraint& linear_constraint,
65 const bool add_cuts,
const bool add_lazy_constraints,
66 const ModelSummary& model_summary) {
70 {.allow_positive_infinity =
false, .allow_negative_infinity =
false}))
71 <<
"invalid linear_constraint coefficients";
73 "cut variables",
"model IDs"));
75 {.allow_positive_infinity = false}))
76 <<
"for GeneratedLinearConstraint.lower_bound";
78 {.allow_negative_infinity = false}))
79 <<
"for GeneratedLinearConstraint.upper_bound";
80 if (linear_constraint.lower_bound() == -
kInf &&
81 linear_constraint.upper_bound() ==
kInf) {
82 return absl::InvalidArgumentError(
83 "invalid GeneratedLinearConstraint, bounds [-inf,inf]");
85 if (linear_constraint.is_lazy() && !add_lazy_constraints) {
86 return absl::InvalidArgumentError(
87 "invalid GeneratedLinearConstraint with lazy attribute set to true, "
88 "adding lazy constraints requires "
89 "CallbackRegistrationProto.add_lazy_constraints=true");
91 if (!linear_constraint.is_lazy() && !add_cuts) {
92 return absl::InvalidArgumentError(
93 "invalid GeneratedLinearConstraint with lazy attribute set to false, "
94 "adding cuts requires CallbackRegistrationProto.add_cuts=true");
96 return absl::OkStatus();
100struct ProtoEnumFormatter {
101 void operator()(std::string*
const out,
const T
value) {
109 const CallbackRegistrationProto& callback_registration,
112 callback_registration.mip_solution_filter(), model_summary.
variables))
113 <<
"invalid CallbackRegistrationProto.mip_solution_filter";
115 callback_registration.mip_node_filter(), model_summary.
variables))
116 <<
"invalid CallbackRegistrationProto.mip_node_filter";
118 const int num_events = callback_registration.request_registration_size();
119 bool can_add_lazy_constraints =
false;
120 bool can_add_cuts =
false;
121 for (
int k = 0; k < num_events; ++k) {
122 const CallbackEventProto requested_event =
123 callback_registration.request_registration(k);
124 if (requested_event == CALLBACK_EVENT_UNSPECIFIED ||
125 !CallbackEventProto_IsValid(requested_event)) {
126 return absl::InvalidArgumentError(absl::StrCat(
127 "invalid event ", requested_event,
" can not be registered"));
129 if (requested_event == CALLBACK_EVENT_MIP_NODE) {
130 can_add_lazy_constraints =
true;
133 if (requested_event == CALLBACK_EVENT_MIP_SOLUTION) {
134 can_add_lazy_constraints =
true;
137 if (callback_registration.add_cuts() && !can_add_cuts) {
138 return absl::InvalidArgumentError(
139 "can only add cuts at event CALLBACK_EVENT_MIP_NODE but this event was "
142 if (callback_registration.add_lazy_constraints() &&
143 !can_add_lazy_constraints) {
144 return absl::InvalidArgumentError(
145 "can only add lazy constraints at events CALLBACK_EVENT_MIP_NODE and "
146 "CALLBACK_EVENT_MIP_SOLUTION but neither of these events were "
150 return absl::OkStatus();
154 const CallbackDataProto& cb_data,
155 const CallbackRegistrationProto& callback_registration,
157 const CallbackEventProto
event = cb_data.event();
159 <<
"invalid CallbackDataProto.event for given CallbackRegistrationProto";
161 const bool has_primal_solution = cb_data.has_primal_solution_vector();
162 if (has_primal_solution && event != CALLBACK_EVENT_MIP_SOLUTION &&
163 event != CALLBACK_EVENT_MIP_NODE) {
164 return absl::InvalidArgumentError(
165 absl::StrCat(
"can't provide primal_solution_vector for event ", event,
169#ifdef RETURN_IF_SCALAR
170#error Collision in macro definition RETURN_IF_SCALAR
172#define RETURN_IF_SCALAR(stat, value, option) \
174 if (stat.has_##value()) { \
175 RETURN_IF_ERROR(CheckScalar(static_cast<double>(stat.value()), option)) \
176 << "Invalid CallbackDataProto." << #stat << "." << #value; \
181 .allow_negative_infinity =
false};
183 .allow_negative =
false};
185 const auto& presolve_stats = cb_data.presolve_stats();
190 const auto& simplex_stats = cb_data.simplex_stats();
197 const auto& barrier_stats = cb_data.barrier_stats();
206 const auto& mip_stats = cb_data.mip_stats();
217 <<
"Invalid CallbackDataProto.runtime.seconds";
219 <<
"Invalid CallbackDataProto.runtime.nanos";
220#undef RETURN_IF_SCALAR
224 case CALLBACK_EVENT_MIP_NODE:
225 case CALLBACK_EVENT_MIP_SOLUTION: {
226 if (has_primal_solution) {
227 const SparseVectorFilterProto& filter =
228 event == CALLBACK_EVENT_MIP_NODE
229 ? callback_registration.mip_node_filter()
230 : callback_registration.mip_solution_filter();
232 cb_data.primal_solution_vector(), filter, model_summary))
233 <<
"invalid CallbackDataProto.primal_solution_vector";
234 }
else if (event == CALLBACK_EVENT_MIP_SOLUTION) {
235 return absl::InvalidArgumentError(
236 absl::StrCat(
"must provide primal_solution_vector for event ",
242 case CALLBACK_EVENT_UNSPECIFIED:
246 <<
"CALLBACK_EVENT_UNSPECIFIED can not be a registered event, this "
247 "points to either an invalid CallbackRegistrationProto (which "
249 "one of the assumptions of this function), or memory corruption";
256 return absl::OkStatus();
260 const CallbackResultProto& callback_result,
261 const CallbackEventProto callback_event,
262 const CallbackRegistrationProto& callback_registration,
266 CHECK_OK(IsEventRegistered(callback_event, callback_registration));
268 if (!callback_result.cuts().empty()) {
269 if (callback_event != CALLBACK_EVENT_MIP_NODE &&
270 callback_event != CALLBACK_EVENT_MIP_SOLUTION) {
271 return absl::InvalidArgumentError(absl::StrCat(
272 "invalid CallbackResultProto, can't return cuts for callback_event ",
275 for (
const CallbackResultProto::GeneratedLinearConstraint& cut :
276 callback_result.cuts()) {
278 cut, callback_registration.add_cuts(),
279 callback_registration.add_lazy_constraints(), model_summary));
282 if (!callback_result.suggested_solutions().empty()) {
283 if (callback_event != CALLBACK_EVENT_MIP_NODE) {
284 return absl::InvalidArgumentError(absl::StrCat(
285 "invalid CallbackResultProto, can't return suggested solutions for "
289 for (
const SparseDoubleVectorProto& primal_solution_vector :
290 callback_result.suggested_solutions()) {
292 primal_solution_vector, SparseVectorFilterProto(), model_summary))
293 <<
"invalid CallbackResultProto.suggested_solutions";
297 return absl::OkStatus();
301 const CallbackRegistrationProto& registration,
302 const absl::flat_hash_set<CallbackEventProto>& supported_events) {
303 std::vector<CallbackEventProto> unsupported_events;
304 for (
const CallbackEventProto event :
EventSet(registration)) {
305 if (!supported_events.contains(event)) {
306 unsupported_events.push_back(event);
310 if (unsupported_events.empty()) {
311 return absl::OkStatus();
314 std::sort(unsupported_events.begin(), unsupported_events.end());
316 const bool plural = unsupported_events.size() >= 2;
317 return absl::InvalidArgumentError(
318 absl::StrCat(
"event", (plural ?
"s { " :
" "),
319 absl::StrJoin(unsupported_events,
", ",
320 ProtoEnumFormatter<CallbackEventProto>()),
321 (plural ?
" } are" :
" is"),
" not supported"));
#define RETURN_IF_SCALAR(stat, value, option)
absl::Span< const double > coefficients
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, 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 ValidateCallbackDataProto(const CallbackDataProto &cb_data, const CallbackRegistrationProto &callback_registration, const ModelSummary &model_summary)
absl::Status ValidateCallbackRegistration(const CallbackRegistrationProto &callback_registration, 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 ValidateSparseVectorFilter(const SparseVectorFilterProto &v, const IdNameBiMap &valid_ids)
absl::Status CheckIdsSubset(absl::Span< const int64_t > ids, const IdNameBiMap &universe, absl::string_view ids_description, absl::string_view universe_description)
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)
#define RETURN_IF_ERROR(expr)
bool allow_positive_infinity