Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event Handler Implementation #292

Merged
merged 9 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
80 changes: 80 additions & 0 deletions test/eventhdlr.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 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
matbesancon marked this conversation as resolved.
Show resolved Hide resolved
@testset "Try To Listen To First LP Solve" begin
matbesancon marked this conversation as resolved.
Show resolved Hide resolved
# 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
Loading