Skip to content

Commit a4e0830

Browse files
authored
Merge pull request #292 from gtjusila/event_handler
Event Handler Implementation
2 parents cb9766a + 2a468a5 commit a4e0830

File tree

6 files changed

+199
-0
lines changed

6 files changed

+199
-0
lines changed

src/MOI_wrapper.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
4747
Dict(),
4848
Dict(),
4949
Dict(),
50+
Dict(),
5051
[],
5152
)
5253

@@ -285,6 +286,7 @@ function MOI.empty!(o::Optimizer)
285286
Dict(),
286287
Dict(),
287288
Dict(),
289+
Dict(),
288290
[],
289291
)
290292
# reapply parameters

src/SCIP.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,6 @@ include("convenience.jl")
4343
# warn about rewrite
4444
include("compat.jl")
4545

46+
# Event handler
47+
include("event_handler.jl")
4648
end

src/event_handler.jl

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Wrapper for implementing event handlers in SCIP.
2+
# Before using please familiaze yourself with https://scipopt.org/doc/html/EVENT.php
3+
#
4+
# The basic idea here is the same as with the separator wrappers. First, you need
5+
# to define a structure that implements the abstract type `AbstractEventhdlr`.
6+
# Second you should implement the function `eventexec` where the argument is an
7+
# instance of your event handler structure. Third, you should at runtime instantiate
8+
# the structure and call `include_event_handler` to register the event handler with SCIP.
9+
#
10+
# See eventhdlr.jl in the test folder for an example.
11+
#
12+
abstract type AbstractEventhdlr end
13+
14+
"""
15+
This is a virtual function that must be implemented by the user. Its Only
16+
argument is the event handler object.
17+
"""
18+
function eventexec(event::T) where {T<:AbstractEventhdlr}
19+
error("eventexec not implemented for type $(T)")
20+
end
21+
22+
"""
23+
This is the function that will be converted to a C function. It signature
24+
matches the one given in the SCIP documentation for SCIP_DECL_EVENTEXEC.
25+
26+
Only the eventhdlr object is passed to the function which is user defined.
27+
"""
28+
function _eventexec(
29+
scip::Ptr{SCIP_},
30+
eventhdlr::Ptr{SCIP_Eventhdlr},
31+
event::Ptr{SCIP_Event},
32+
eventdata::Ptr{SCIP_EventData},
33+
)
34+
# Get Julia object out of eventhandler data
35+
data::Ptr{SCIP_EventData} = SCIPeventhdlrGetData(eventhdlr)
36+
event = unsafe_pointer_to_objref(data)
37+
38+
#call user method
39+
eventexec(event)
40+
41+
return SCIP_OKAY
42+
end
43+
44+
"""
45+
include_event_handler(scipd::SCIP.SCIPData, event_handler::EVENTHDLR; name="", desc="")
46+
47+
Include the event handler in SCIP. WARNING! In contrast to the separator wrapper you only need to
48+
pass the SCIPData rather than the SCIP pointer and dictionary.
49+
50+
# Arguments
51+
- scipd::SCIP.SCIPData: The SCIPData object
52+
- event_handler::EVENTHDLR: The event handler object
53+
- name::String: The name of the event handler
54+
- desc::String: The description of the event handler
55+
"""
56+
function include_event_handler(
57+
scipd::SCIP.SCIPData,
58+
event_handler::EVENTHDLR;
59+
name="",
60+
desc="",
61+
) where {EVENTHDLR<:AbstractEventhdlr}
62+
_eventexec = @cfunction(
63+
_eventexec,
64+
SCIP_RETCODE,
65+
(Ptr{SCIP_}, Ptr{SCIP_Eventhdlr}, Ptr{SCIP_Event}, Ptr{SCIP_EventData})
66+
)
67+
68+
eventhdlrptr = Ref{Ptr{SCIP_Eventhdlr}}(C_NULL)
69+
eventhdlr = pointer_from_objref(event_handler)
70+
71+
if name == ""
72+
name = "__eventhdlr__$(length(eventhdlrs))"
73+
end
74+
75+
@SCIP_CALL SCIPincludeEventhdlrBasic(
76+
scipd.scip[],
77+
eventhdlrptr,
78+
name,
79+
desc,
80+
_eventexec,
81+
eventhdlr,
82+
)
83+
84+
@assert eventhdlrptr[] != C_NULL
85+
#Persist in scip store against GC
86+
scipd.eventhdlrs[event_handler] = eventhdlrptr[]
87+
end
88+
89+
"""
90+
catch_event(scipd::SCIP.SCIPData, eventtype::SCIP_EVENTTYPE, eventhdlr::EVENTHDLR)
91+
92+
Catch an event in SCIP. This function is a wrapper around the SCIPcatchEvent function.
93+
Warning! This function should only be called after the SCIP has been transformed.
94+
"""
95+
function catch_event(
96+
scipd::SCIP.SCIPData,
97+
eventtype::SCIP_EVENTTYPE,
98+
eventhdlr::EVENTHDLR,
99+
) where {EVENTHDLR<:AbstractEventhdlr}
100+
@assert SCIPgetStage(scipd) != SCIP_STAGE_INIT
101+
@assert SCIPgetStage(scipd) != SCIP_STAGE_PROBLEM
102+
eventhdlrptr = scipd.eventhdlrs[eventhdlr]
103+
@SCIP_CALL SCIPcatchEvent(scipd, eventtype, eventhdlrptr, C_NULL, C_NULL)
104+
end

src/scip_data.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ mutable struct SCIPData
3434
scip::Ref{Ptr{SCIP_}}
3535
vars::Dict{VarRef,Ref{Ptr{SCIP_VAR}}}
3636
conss::Dict{ConsRef,Ref{Ptr{SCIP_CONS}}}
37+
3738
var_count::Int64
3839
cons_count::Int64
3940

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

52+
# Map from user-defined types (keys are <: AbstractEventHandler)
53+
# to the corresponding SCIP objects.
54+
eventhdlrs::Dict{Any,Ptr{SCIP_Eventhdlr}}
55+
5156
# User-defined cut selectors and branching rules
5257
cutsel_storage::Dict{Any,Ptr{SCIP_CUTSEL}}
5358
branchrule_storage::Dict{Any,Ptr{SCIP_BRANCHRULE}}

test/eventhdlr.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# A simple testcase to test the event handler functionality.
2+
# It is assumed that test/sepa_support.jl is already included
3+
using SCIP
4+
import MathOptInterface as MOI
5+
6+
module FirstLPEventTest
7+
# A simple event handler that stores the objective value of the first LP solve at the root node
8+
using SCIP
9+
10+
mutable struct FirstLPEvent <: SCIP.AbstractEventhdlr
11+
scip::SCIP.SCIPData
12+
firstlpobj::Float64
13+
end
14+
15+
function SCIP.eventexec(event::FirstLPEvent)
16+
# Only consider the root node
17+
current_node = SCIP.SCIPgetFocusNode(event.scip)
18+
depth = SCIP.SCIPnodeGetDepth(current_node)
19+
if depth == 0
20+
scip = event.scip
21+
event.firstlpobj = SCIP.SCIPgetLPObjval(scip)
22+
end
23+
end
24+
end
25+
26+
@testset "Listen to first LP solve" begin
27+
# create an empty problem
28+
optimizer = SCIP.Optimizer()
29+
inner = optimizer.inner
30+
sepa_set_scip_parameters((par, val) -> SCIP.set_parameter(inner, par, val))
31+
32+
# add variables
33+
x, y = MOI.add_variables(optimizer, 2)
34+
MOI.add_constraint(optimizer, x, MOI.ZeroOne())
35+
MOI.add_constraint(optimizer, y, MOI.ZeroOne())
36+
37+
# add constraint: x + y ≤ 1.5
38+
MOI.add_constraint(
39+
optimizer,
40+
MOI.ScalarAffineFunction(
41+
MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]),
42+
0.0,
43+
),
44+
MOI.LessThan(1.5),
45+
)
46+
47+
# minimize -x - y
48+
MOI.set(
49+
optimizer,
50+
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
51+
MOI.ScalarAffineFunction(
52+
MOI.ScalarAffineTerm.([-1.0, -1.0], [x, y]),
53+
0.0,
54+
),
55+
)
56+
MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)
57+
58+
# add eventhandler
59+
eventhdlr = FirstLPEventTest.FirstLPEvent(inner, 10)
60+
SCIP.include_event_handler(
61+
inner,
62+
eventhdlr;
63+
name="firstlp",
64+
desc="Store the objective value of the first LP solve at the root node",
65+
)
66+
67+
# transform the problem into SCIP
68+
SCIP.@SCIP_CALL SCIP.SCIPtransformProb(inner)
69+
70+
# catch the event. Again this can only be done after the problem is transformed
71+
SCIP.catch_event(inner, SCIP.SCIP_EVENTTYPE_FIRSTLPSOLVED, eventhdlr)
72+
73+
# solve the problem
74+
SCIP.@SCIP_CALL SCIP.SCIPsolve(inner.scip[])
75+
76+
# test if the event handler worked
77+
@test eventhdlr.firstlpobj != 10
78+
79+
# free the problem
80+
finalize(inner)
81+
end

test/runtests.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ include("sepa_support.jl")
4343
@testset "separators" begin
4444
include("sepa.jl")
4545
end
46+
47+
@testset "event handlers" begin
48+
include("eventhdlr.jl")
49+
end
50+
4651
@testset "cut callbacks" begin
4752
include("cutcallback.jl")
4853
end

0 commit comments

Comments
 (0)