Skip to content

Commit 117cfc4

Browse files
authored
Merge branch 'master' into od/row-type
2 parents dcbd43a + 7a253c7 commit 117cfc4

File tree

2 files changed

+89
-12
lines changed

2 files changed

+89
-12
lines changed

src/MOI_wrapper.jl

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,14 +2151,30 @@ function MOI.optimize!(model::Optimizer)
21512151
ret = Highs_zeroAllClocks(model)
21522152
_check_ret(ret)
21532153
end
2154+
has_default_callback = model.callback_data === nothing
2155+
if has_default_callback
2156+
# From the docstring of disable_sigint, "External functions that do not
2157+
# call julia code or julia runtime automatically disable sigint during
2158+
# their execution." We don't want this though! We want to be able to
2159+
# SIGINT HiGHS, and then catch it as an interrupt. As a hack, until
2160+
# Julia introduces an interruptible ccall --- which it likely won't
2161+
# https://github.com/JuliaLang/julia/issues/2622 --- set a null
2162+
# callback.
2163+
MOI.set(model, CallbackFunction(), (args...) -> Cint(0))
2164+
end
21542165
# if `Highs_run` implicitly uses memory or other resources owned by `model`, preserve it
21552166
GC.@preserve model begin
21562167
# allow Julia to GC while this thread is in `Highs_run`
21572168
gc_state = ccall(:jl_gc_safe_enter, Int8, ())
2158-
ret = Highs_run(model)
2169+
# We disable sigint here so that it can be called only when we are in a
2170+
# try-catch of our CallbackFunction.
2171+
ret = disable_sigint(() -> Highs_run(model))
21592172
# leave GC-safe region, waiting for GC to complete if it's running
21602173
ccall(:jl_gc_safe_leave, Cvoid, (Int8,), gc_state)
21612174
end
2175+
if has_default_callback
2176+
MOI.set(model, CallbackFunction(), nothing)
2177+
end
21622178
_store_solution(model, ret)
21632179
# TODO(odow): resetting the bounds here invalidates previously stored
21642180
# solutions.
@@ -3088,6 +3104,8 @@ end
30883104
kHighsCallbackMipImprovingSolution,
30893105
# kHighsCallbackMipLogging,
30903106
kHighsCallbackMipInterrupt,
3107+
kHighsCallbackMipGetCutPool,
3108+
kHighsCallbackMipDefineLazyConstraints,
30913109
],
30923110
) <: MOI.AbstractModelAttribute
30933111
@@ -3135,24 +3153,41 @@ function CallbackFunction()
31353153
kHighsCallbackMipImprovingSolution,
31363154
# kHighsCallbackMipLogging,
31373155
kHighsCallbackMipInterrupt,
3156+
kHighsCallbackMipGetCutPool,
3157+
kHighsCallbackMipDefineLazyConstraints,
31383158
])
31393159
end
31403160

31413161
function _cfn_user_callback(
31423162
callback_type::Cint,
31433163
message::Ptr{Cchar},
3144-
p_data_out::Ptr{HiGHS.HighsCallbackDataOut},
3145-
p_data_in::Ptr{HiGHS.HighsCallbackDataIn},
3146-
user_callback_data::Ptr{Cvoid},
3147-
)
3148-
user_data = unsafe_pointer_to_objref(user_callback_data)::_CallbackData
3149-
terminate = user_data.f(
3150-
callback_type,
3151-
message,
3152-
unsafe_load(p_data_out)::HiGHS.HighsCallbackDataOut,
3164+
p_data_out::Ptr{HighsCallbackDataOut},
3165+
p_data_in::Ptr{HighsCallbackDataIn},
3166+
p_user_data::Ptr{Cvoid},
3167+
)
3168+
user_data = unsafe_pointer_to_objref(p_user_data)::_CallbackData
3169+
data_out = unsafe_load(p_data_out)::HighsCallbackDataOut
3170+
if callback_type in (
3171+
kHighsCallbackSimplexInterrupt,
3172+
kHighsCallbackIpmInterrupt,
3173+
kHighsCallbackMipInterrupt,
31533174
)
3154-
if p_data_in != C_NULL
3155-
unsafe_store!(p_data_in, HighsCallbackDataIn(terminate))
3175+
@assert p_data_in !== C_NULL
3176+
try
3177+
reenable_sigint() do
3178+
terminate = user_data.f(callback_type, message, data_out)
3179+
unsafe_store!(p_data_in, HighsCallbackDataIn(terminate))
3180+
return
3181+
end
3182+
catch err
3183+
unsafe_store!(p_data_in, HighsCallbackDataIn(Cint(1)))
3184+
if !(err isa InterruptException)
3185+
rethrow(err)
3186+
end
3187+
end
3188+
else
3189+
# Ignore what the user says about terminating
3190+
_ = user_data.f(callback_type, message, data_out)
31563191
end
31573192
return
31583193
end
@@ -3179,6 +3214,17 @@ function MOI.set(model::Optimizer, attr::CallbackFunction, f::Function)
31793214
return
31803215
end
31813216

3217+
function MOI.set(model::Optimizer, attr::CallbackFunction, ::Nothing)
3218+
model.callback_data = nothing
3219+
ret = Highs_setCallback(model, C_NULL, C_NULL)
3220+
_check_ret(ret)
3221+
for callback_type in attr.callback_types
3222+
ret = Highs_stopCallback(model, callback_type)
3223+
_check_ret(ret)
3224+
end
3225+
return
3226+
end
3227+
31823228
function MOI.write_to_file(model::Optimizer, filename::String)
31833229
ret = Highs_writeModel(model, filename)
31843230
_check_ret(ret)

test/MOI_wrapper.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,37 @@ function test_write_to_file()
10501050
return
10511051
end
10521052

1053+
function test_callback_ctrl_c()
1054+
model = HiGHS.Optimizer()
1055+
MOI.set(model, MOI.RawOptimizerAttribute("solver"), "ipm")
1056+
MOI.set(model, MOI.RawOptimizerAttribute("presolve"), "off")
1057+
x = MOI.add_variables(model, 3)
1058+
c = MOI.add_constraint.(model, 1.0 .* x, MOI.EqualTo.(1.0:3.0))
1059+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
1060+
f = 1.0 * x[1] + x[2] + x[3]
1061+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
1062+
user_callback = (args...) -> throw(InterruptException())
1063+
MOI.set(model, HiGHS.CallbackFunction(), user_callback)
1064+
MOI.optimize!(model)
1065+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.INTERRUPTED
1066+
return
1067+
end
1068+
1069+
function test_callback_error()
1070+
model = HiGHS.Optimizer()
1071+
MOI.set(model, MOI.RawOptimizerAttribute("solver"), "ipm")
1072+
MOI.set(model, MOI.RawOptimizerAttribute("presolve"), "off")
1073+
x = MOI.add_variables(model, 3)
1074+
c = MOI.add_constraint.(model, 1.0 .* x, MOI.EqualTo.(1.0:3.0))
1075+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
1076+
f = 1.0 * x[1] + x[2] + x[3]
1077+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
1078+
user_callback = (args...) -> error("ABC")
1079+
MOI.set(model, HiGHS.CallbackFunction(), user_callback)
1080+
@test_throws ErrorException("ABC") MOI.optimize!(model)
1081+
return
1082+
end
1083+
10531084
function test_row_type()
10541085
@test HiGHS._row_type(MOI.GreaterThan(0.0)) == HiGHS._ROW_TYPE_GREATERTHAN
10551086
@test HiGHS._row_type(MOI.LessThan(0.0)) == HiGHS._ROW_TYPE_LESSTHAN

0 commit comments

Comments
 (0)