Skip to content

Commit

Permalink
Merge pull request #292 from gtjusila/event_handler
Browse files Browse the repository at this point in the history
Event Handler Implementation
  • Loading branch information
matbesancon committed Jul 26, 2024
2 parents cb9766a + 2a468a5 commit a4e0830
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
Dict(),
Dict(),
Dict(),
Dict(),
[],
)

Expand Down Expand Up @@ -285,6 +286,7 @@ function MOI.empty!(o::Optimizer)
Dict(),
Dict(),
Dict(),
Dict(),
[],
)
# reapply parameters
Expand Down
2 changes: 2 additions & 0 deletions src/SCIP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ include("convenience.jl")
# warn about rewrite
include("compat.jl")

# Event handler
include("event_handler.jl")
end
104 changes: 104 additions & 0 deletions src/event_handler.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Wrapper for implementing event handlers in SCIP.
# Before using please familiaze yourself with https://scipopt.org/doc/html/EVENT.php
#
# The basic idea here is the same as with the separator wrappers. First, you need
# to define a structure that implements the abstract type `AbstractEventhdlr`.
# Second you should implement the function `eventexec` where the argument is an
# instance of your event handler structure. Third, you should at runtime instantiate
# the structure and call `include_event_handler` to register the event handler with SCIP.
#
# See eventhdlr.jl in the test folder for an example.
#
abstract type AbstractEventhdlr end

"""
This is a virtual function that must be implemented by the user. Its Only
argument is the event handler object.
"""
function eventexec(event::T) where {T<:AbstractEventhdlr}
error("eventexec not implemented for type $(T)")
end

"""
This is the function that will be converted to a C function. It signature
matches the one given in the SCIP documentation for SCIP_DECL_EVENTEXEC.
Only the eventhdlr object is passed to the function which is user defined.
"""
function _eventexec(
scip::Ptr{SCIP_},
eventhdlr::Ptr{SCIP_Eventhdlr},
event::Ptr{SCIP_Event},
eventdata::Ptr{SCIP_EventData},
)
# Get Julia object out of eventhandler data
data::Ptr{SCIP_EventData} = SCIPeventhdlrGetData(eventhdlr)
event = unsafe_pointer_to_objref(data)

#call user method
eventexec(event)

return SCIP_OKAY
end

"""
include_event_handler(scipd::SCIP.SCIPData, event_handler::EVENTHDLR; name="", desc="")
Include the event handler in SCIP. WARNING! In contrast to the separator wrapper you only need to
pass the SCIPData rather than the SCIP pointer and dictionary.
# Arguments
- scipd::SCIP.SCIPData: The SCIPData object
- event_handler::EVENTHDLR: The event handler object
- name::String: The name of the event handler
- desc::String: The description of the event handler
"""
function include_event_handler(
scipd::SCIP.SCIPData,
event_handler::EVENTHDLR;
name="",
desc="",
) where {EVENTHDLR<:AbstractEventhdlr}
_eventexec = @cfunction(
_eventexec,
SCIP_RETCODE,
(Ptr{SCIP_}, Ptr{SCIP_Eventhdlr}, Ptr{SCIP_Event}, Ptr{SCIP_EventData})
)

eventhdlrptr = Ref{Ptr{SCIP_Eventhdlr}}(C_NULL)
eventhdlr = pointer_from_objref(event_handler)

if name == ""
name = "__eventhdlr__$(length(eventhdlrs))"
end

@SCIP_CALL SCIPincludeEventhdlrBasic(
scipd.scip[],
eventhdlrptr,
name,
desc,
_eventexec,
eventhdlr,
)

@assert eventhdlrptr[] != C_NULL
#Persist in scip store against GC
scipd.eventhdlrs[event_handler] = eventhdlrptr[]
end

"""
catch_event(scipd::SCIP.SCIPData, eventtype::SCIP_EVENTTYPE, eventhdlr::EVENTHDLR)
Catch an event in SCIP. This function is a wrapper around the SCIPcatchEvent function.
Warning! This function should only be called after the SCIP has been transformed.
"""
function catch_event(
scipd::SCIP.SCIPData,
eventtype::SCIP_EVENTTYPE,
eventhdlr::EVENTHDLR,
) where {EVENTHDLR<:AbstractEventhdlr}
@assert SCIPgetStage(scipd) != SCIP_STAGE_INIT
@assert SCIPgetStage(scipd) != SCIP_STAGE_PROBLEM
eventhdlrptr = scipd.eventhdlrs[eventhdlr]
@SCIP_CALL SCIPcatchEvent(scipd, eventtype, eventhdlrptr, C_NULL, C_NULL)
end
5 changes: 5 additions & 0 deletions src/scip_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mutable struct SCIPData
scip::Ref{Ptr{SCIP_}}
vars::Dict{VarRef,Ref{Ptr{SCIP_VAR}}}
conss::Dict{ConsRef,Ref{Ptr{SCIP_CONS}}}

var_count::Int64
cons_count::Int64

Expand All @@ -48,6 +49,10 @@ mutable struct SCIPData
# corresponding SCIP objects.
sepas::Dict{Any,Ptr{SCIP_SEPA}}

# Map from user-defined types (keys are <: AbstractEventHandler)
# to the corresponding SCIP objects.
eventhdlrs::Dict{Any,Ptr{SCIP_Eventhdlr}}

# User-defined cut selectors and branching rules
cutsel_storage::Dict{Any,Ptr{SCIP_CUTSEL}}
branchrule_storage::Dict{Any,Ptr{SCIP_BRANCHRULE}}
Expand Down
81 changes: 81 additions & 0 deletions test/eventhdlr.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# A simple testcase to test the event handler functionality.
# It is assumed that test/sepa_support.jl is already included
using SCIP
import MathOptInterface as MOI

module FirstLPEventTest
# A simple event handler that stores the objective value of the first LP solve at the root node
using SCIP

mutable struct FirstLPEvent <: SCIP.AbstractEventhdlr
scip::SCIP.SCIPData
firstlpobj::Float64
end

function SCIP.eventexec(event::FirstLPEvent)
# Only consider the root node
current_node = SCIP.SCIPgetFocusNode(event.scip)
depth = SCIP.SCIPnodeGetDepth(current_node)
if depth == 0
scip = event.scip
event.firstlpobj = SCIP.SCIPgetLPObjval(scip)
end
end
end

@testset "Listen to first LP solve" begin
# create an empty problem
optimizer = SCIP.Optimizer()
inner = optimizer.inner
sepa_set_scip_parameters((par, val) -> SCIP.set_parameter(inner, par, val))

# add variables
x, y = MOI.add_variables(optimizer, 2)
MOI.add_constraint(optimizer, x, MOI.ZeroOne())
MOI.add_constraint(optimizer, y, MOI.ZeroOne())

# add constraint: x + y ≤ 1.5
MOI.add_constraint(
optimizer,
MOI.ScalarAffineFunction(
MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]),
0.0,
),
MOI.LessThan(1.5),
)

# minimize -x - y
MOI.set(
optimizer,
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
MOI.ScalarAffineFunction(
MOI.ScalarAffineTerm.([-1.0, -1.0], [x, y]),
0.0,
),
)
MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)

# add eventhandler
eventhdlr = FirstLPEventTest.FirstLPEvent(inner, 10)
SCIP.include_event_handler(
inner,
eventhdlr;
name="firstlp",
desc="Store the objective value of the first LP solve at the root node",
)

# transform the problem into SCIP
SCIP.@SCIP_CALL SCIP.SCIPtransformProb(inner)

# catch the event. Again this can only be done after the problem is transformed
SCIP.catch_event(inner, SCIP.SCIP_EVENTTYPE_FIRSTLPSOLVED, eventhdlr)

# solve the problem
SCIP.@SCIP_CALL SCIP.SCIPsolve(inner.scip[])

# test if the event handler worked
@test eventhdlr.firstlpobj != 10

# free the problem
finalize(inner)
end
5 changes: 5 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ include("sepa_support.jl")
@testset "separators" begin
include("sepa.jl")
end

@testset "event handlers" begin
include("eventhdlr.jl")
end

@testset "cut callbacks" begin
include("cutcallback.jl")
end
Expand Down

0 comments on commit a4e0830

Please sign in to comment.