diff --git a/ortools/gscip/gscip.cc b/ortools/gscip/gscip.cc index 56da6d2662..5bc117f706 100644 --- a/ortools/gscip/gscip.cc +++ b/ortools/gscip/gscip.cc @@ -785,8 +785,8 @@ absl::StatusOr GScip::Solve( // handler. using internal::CaptureMessageHandlerPtr; using internal::MessageHandlerPtr; - MessageHandlerPtr previous_handler = CaptureMessageHandlerPtr(nullptr); - MessageHandlerPtr new_handler = CaptureMessageHandlerPtr(nullptr); + MessageHandlerPtr previous_handler; + MessageHandlerPtr new_handler; if (message_handler != nullptr) { previous_handler = CaptureMessageHandlerPtr(SCIPgetMessagehdlr(scip_)); ASSIGN_OR_RETURN(new_handler, diff --git a/ortools/gscip/gscip_event_handler.cc b/ortools/gscip/gscip_event_handler.cc index f96ba9d3a2..c53c2382f3 100644 --- a/ortools/gscip/gscip_event_handler.cc +++ b/ortools/gscip/gscip_event_handler.cc @@ -13,8 +13,10 @@ #include "ortools/gscip/gscip_event_handler.h" +#include +#include + #include "absl/status/status.h" -#include "absl/strings/string_view.h" #include "ortools/base/logging.h" #include "ortools/gscip/gscip.h" #include "ortools/linear_solver/scip_helper_macros.h" @@ -36,13 +38,12 @@ static SCIP_DECL_EVENTEXEC(EventExec) { CHECK_NE(eventhdlr, nullptr); CHECK_NE(event, nullptr); - SCIP_EVENTHDLRDATA* event_handler_data = SCIPeventhdlrGetData(eventhdlr); + SCIP_EVENTHDLRDATA* const event_handler_data = + SCIPeventhdlrGetData(eventhdlr); CHECK_NE(event_handler_data, nullptr); - operations_research::GScipEventHandler* handler = event_handler_data->handler; - handler->Execute(operations_research::GScipEventHandlerContext( - event_handler_data->gscip, SCIPeventGetType(event))); - return SCIP_OKAY; + return event_handler_data->handler->Execute( + {event_handler_data->gscip, SCIPeventGetType(event)}); } static SCIP_DECL_EVENTINIT(EventInit) { @@ -50,15 +51,25 @@ static SCIP_DECL_EVENTINIT(EventInit) { CHECK_NE(scip, nullptr); CHECK_NE(eventhdlr, nullptr); - SCIP_EVENTHDLRDATA* event_handler_data = SCIPeventhdlrGetData(eventhdlr); + SCIP_EVENTHDLRDATA* const event_handler_data = + SCIPeventhdlrGetData(eventhdlr); CHECK_NE(event_handler_data, nullptr); - operations_research::GScipEventHandler* handler = event_handler_data->handler; - for (const SCIP_EVENTTYPE event_type : - handler->description().events_to_catch_from_start) { - SCIP_CALL(SCIPcatchEvent(scip, event_type, eventhdlr, nullptr, nullptr)); - } - return SCIP_OKAY; + return event_handler_data->handler->Init(event_handler_data->gscip); +} + +static SCIP_DECL_EVENTEXIT(EventExit) { + VLOG(3) << "EventExit"; + CHECK_NE(scip, nullptr); + CHECK_NE(eventhdlr, nullptr); + + SCIP_EVENTHDLRDATA* const event_handler_data = + SCIPeventhdlrGetData(eventhdlr); + CHECK_NE(event_handler_data, nullptr); + + SCIP_CALL(DropAllEvents(*event_handler_data->handler)); + + return event_handler_data->handler->Exit(event_handler_data->gscip); } static SCIP_DECL_EVENTFREE(EventFree) { @@ -66,30 +77,65 @@ static SCIP_DECL_EVENTFREE(EventFree) { CHECK_NE(scip, nullptr); CHECK_NE(eventhdlr, nullptr); - SCIP_EVENTHDLRDATA* event_handler_data = SCIPeventhdlrGetData(eventhdlr); + SCIP_EVENTHDLRDATA* const event_handler_data = + SCIPeventhdlrGetData(eventhdlr); CHECK_NE(event_handler_data, nullptr); + delete event_handler_data; SCIPeventhdlrSetData(eventhdlr, nullptr); + return SCIP_OKAY; } namespace operations_research { -void RegisterGScipEventHandler(GScip* scip, GScipEventHandler* handler) { +void GScipEventHandler::Register(GScip* const gscip) { + CHECK_EQ(gscip_, nullptr) << "Already registered."; + CHECK_EQ(event_handler_, nullptr); + + gscip_ = gscip; + // event_handler_data is freed in EventFree. - SCIP_EVENTHDLRDATA* event_handler_data = new SCIP_EVENTHDLRDATA; - event_handler_data->gscip = scip; - event_handler_data->handler = handler; - SCIP_EVENTHDLR* event_handler = nullptr; + SCIP_EVENTHDLRDATA* const event_handler_data = new SCIP_EVENTHDLRDATA; + event_handler_data->gscip = gscip; + event_handler_data->handler = this; + CHECK_OK(SCIP_TO_STATUS(SCIPincludeEventhdlrBasic( - scip->scip(), &event_handler, handler->description().name.c_str(), - handler->description().description.c_str(), EventExec, - event_handler_data))); - CHECK_NE(event_handler, nullptr); + gscip->scip(), &event_handler_, description_.name.c_str(), + description_.description.c_str(), EventExec, event_handler_data))); + CHECK_NE(event_handler_, nullptr); + CHECK_OK(SCIP_TO_STATUS( - SCIPsetEventhdlrInit(scip->scip(), event_handler, EventInit))); + SCIPsetEventhdlrInit(gscip->scip(), event_handler_, EventInit))); CHECK_OK(SCIP_TO_STATUS( - SCIPsetEventhdlrFree(scip->scip(), event_handler, EventFree))); + SCIPsetEventhdlrExit(gscip->scip(), event_handler_, EventExit))); + CHECK_OK(SCIP_TO_STATUS( + SCIPsetEventhdlrFree(gscip->scip(), event_handler_, EventFree))); +} + +SCIP_RETCODE GScipEventHandler::CatchEvent(const SCIP_EVENTTYPE event_type) { + int filter_pos = -1; + + SCIP_CALL(SCIPcatchEvent(gscip_->scip(), event_type, event_handler_, + /*eventdata=*/nullptr, &filter_pos)); + CHECK_GE(filter_pos, 0); + + caught_events_.emplace_back(event_type, filter_pos); + + return SCIP_OKAY; +} + +SCIP_RETCODE DropAllEvents(GScipEventHandler& handler) { + for (const GScipEventHandler::CaughtEvent& caught_event : + handler.caught_events_) { + SCIP_CALL(SCIPdropEvent(handler.gscip_->scip(), caught_event.event_type, + handler.event_handler_, + /*eventdata=*/nullptr, caught_event.filter_pos)); + } + + handler.caught_events_.clear(); + + return SCIP_OKAY; } } // namespace operations_research diff --git a/ortools/gscip/gscip_event_handler.h b/ortools/gscip/gscip_event_handler.h index 4a4cabff02..cc3c3bb330 100644 --- a/ortools/gscip/gscip_event_handler.h +++ b/ortools/gscip/gscip_event_handler.h @@ -35,16 +35,6 @@ struct GScipEventHandlerDescription { // See CONSHDLR_DESC in SCIP documentation above. std::string description; - - // These are "general" events to be added via SCIPcatchEvent in the EVENTINIT - // callback, not "Var" or "Row" events. See scip/type_event.h for the list of - // possible events. - std::vector events_to_catch_from_start; - - // TODO(user,user): Support Var and Row events. - - // TODO(user,user): Support registering events in the EVENTINITSOL - // callback, which would cause them to be trapped only after presolve. }; // Passed by value. This is a lightweight interface to the callback context and @@ -66,28 +56,137 @@ class GScipEventHandlerContext { // queried within an event handler. private: - GScip* gscip_; - SCIP_EVENTTYPE event_type_; + GScip* const gscip_; + const SCIP_EVENTTYPE event_type_; }; -// Inherit from this to implement the callback. +// Inherit from this to implement the callback and override Init() to call +// CatchEvent() for the events you want to listen to. +// +// Usage: +// +// class MyHandler : public GScipEventHandler { +// public: +// MyHandler() +// : GScipEventHandler({ +// .name = "my handler", .description = "something"}) {} +// +// SCIP_RETCODE Init(GScip* const gscip) override { +// SCIP_CALL(CatchEvent(SCIP_EVENTTYPE_SOLFOUND)); +// return SCIP_OKAY; +// } +// +// SCIP_RETCODE Execute(const GScipEventHandlerContext context) override { +// ... +// return SCIP_OKAY; +// } +// }; +// +// std::unique_ptr gscip = ...; +// +// MyHandler handler; +// handler.Register(gscip.get()); +// class GScipEventHandler { public: explicit GScipEventHandler(const GScipEventHandlerDescription& description) : description_(description) {} - virtual ~GScipEventHandler() {} - const GScipEventHandlerDescription& description() const { - return description_; + + virtual ~GScipEventHandler() = default; + + // Registers this event handler to the given GScip. + // + // This function CHECKs that this handler has not been previously registered. + // + // The given GScip won't own this handler but will keep a pointer to it that + // will be used during the solve. + void Register(GScip* gscip); + + // Initialization of the event handler. Called after the problem was + // transformed. + // + // The implementation should use the CatchEvent() method to register to global + // events. + // + // Return SCIP_OKAY, or use SCIP_CALL macro to wraps SCIP calls to properly + // propagate errors. + virtual SCIP_RETCODE Init(GScip* gscip) { return SCIP_OKAY; } + + // Called when a caught event is emitted. + // + // Return SCIP_OKAY, or use SCIP_CALL macro to wraps SCIP calls to properly + // propagate errors. + virtual SCIP_RETCODE Execute(GScipEventHandlerContext context) { + return SCIP_OKAY; } - virtual void Execute(GScipEventHandlerContext context) = 0; + // Deinitialization of the event handler. + // + // Called before the transformed problem is freed and after all the events + // specified in CatchEvent() have been dropped (thus there is no need to + // implement this function to drop these events since this would have already + // been done). + // + // Return SCIP_OKAY, or use SCIP_CALL macro to wraps SCIP calls to properly + // propagate errors. + virtual SCIP_RETCODE Exit(GScip* gscip) { return SCIP_OKAY; } + + protected: + // Catches a global event (i.e. not a variable or row depedent one) based on + // the input event_type mask. + // + // This method must only be called after the problem is transformed; typically + // it is called in the Init() virtual method. + // + // Caught events will be automatically dropped when the handler will be called + // on EXIT (before calling the corresponding Exit() member function). + // + // See scip/type_event.h for the list of possible events. This function + // corresponds to SCIPcatchEvent(). + // + // TODO(user,user): Support Var and Row events. + // + // TODO(user,user): Support registering events in the EVENTINITSOL + // callback, which would cause them to be trapped only after presolve. + SCIP_RETCODE CatchEvent(SCIP_EVENTTYPE event_type); private: - GScipEventHandlerDescription description_; -}; + struct CaughtEvent { + CaughtEvent(const SCIP_EVENTTYPE event_type, const int filter_pos) + : event_type(event_type), filter_pos(filter_pos) {} -// Handler is not owned but held. -void RegisterGScipEventHandler(GScip* scip, GScipEventHandler* handler); + // The event_type mask for this catch. + SCIP_EVENTTYPE event_type; + + // The key used by SCIP to identify this catch with SCIPdropEvent(). Using + // this key prevents SCIP from having to do a look up to find the catch and + // helps when there are duplicates. + // + // It is the index of the data associated to the catch in the array SCIP + // uses as storage (this index is stable, even after other catch added + // previously are removed, since SCIP maintains a free-list of removed items + // instead of renumbering all elements). + int filter_pos; + }; + + // Calls SCIPdropEvent() for all events in caught_events_ and clear this + // collection. + // + // This is not a member function since it needs to be visible to the SCIP Exit + // callback function. + friend SCIP_RETCODE DropAllEvents(GScipEventHandler& handler); + + const GScipEventHandlerDescription description_; + + // Pointer to GScip set by Register(); + GScip* gscip_ = nullptr; + + // Pointer to the event handler registered on SCIP. + SCIP_EVENTHDLR* event_handler_ = nullptr; + + // Caught events via CatchEvent(). + std::vector caught_events_; +}; } // namespace operations_research diff --git a/ortools/gscip/gscip_message_handler.cc b/ortools/gscip/gscip_message_handler.cc index 1aea0a84fb..4d57904af0 100644 --- a/ortools/gscip/gscip_message_handler.cc +++ b/ortools/gscip/gscip_message_handler.cc @@ -104,25 +104,19 @@ void SCIPMessageHandlerWarning(SCIP_MESSAGEHDLR* const handler, FILE*, message); } -// Release the input message handler. -// -// This function is a wrapper around SCIPmessagehdlrRelease which, in addition -// to releasing the handler, also resets the pointer to it, thus has a pointer -// on pointer argument. We can't use that as a deleter for a unique_ptr as in -// MessageHandlerPtr since we need a function that a pointer argument. -void ReleaseSCIPMessageHandler(SCIP_MESSAGEHDLR* handler) { +} // namespace + +void ReleaseSCIPMessageHandler::operator()(SCIP_MESSAGEHDLR* handler) const { if (handler != nullptr) { CHECK_EQ(SCIPmessagehdlrRelease(&handler), SCIP_OKAY); } } -} // namespace - MessageHandlerPtr CaptureMessageHandlerPtr(SCIP_MESSAGEHDLR* const handler) { if (handler != nullptr) { SCIPmessagehdlrCapture(handler); } - return {handler, ReleaseSCIPMessageHandler}; + return MessageHandlerPtr(handler); } absl::StatusOr MakeSCIPMessageHandler( @@ -146,7 +140,7 @@ absl::StatusOr MakeSCIPMessageHandler( // the unique_ptr since the ownership has been transferred to SCIP. data.release(); - return MessageHandlerPtr{message_handler, ReleaseSCIPMessageHandler}; + return MessageHandlerPtr(message_handler); } ScopedSCIPMessageHandlerDisabler::ScopedSCIPMessageHandlerDisabler( diff --git a/ortools/gscip/gscip_message_handler.h b/ortools/gscip/gscip_message_handler.h index b5a75cab64..361f8fa973 100644 --- a/ortools/gscip/gscip_message_handler.h +++ b/ortools/gscip/gscip_message_handler.h @@ -38,12 +38,21 @@ using GScipMessageHandler = namespace internal { +// Functor that releases the input message handler if not nullptr. It is used as +// the deleter for the MessageHandlerPtr unique_ptr. +// +// It is a wrapper around SCIPmessagehdlrRelease. +struct ReleaseSCIPMessageHandler { + void operator()(SCIP_MESSAGEHDLR* handler) const; +}; + // A unique_ptr that releases a SCIP message handler when destroyed. // -// Use CaptureMessageHandlerPtr() to create values of this type, capturing their -// input. +// Use CaptureMessageHandlerPtr() to create to capture an existing message +// handler and creates this automatic pointer that will released it on +// destruction. using MessageHandlerPtr = - std::unique_ptr; + std::unique_ptr; // Captures the input handler and returns a unique pointer that will release it // when destroyed.