Skip to content

Commit

Permalink
Fix callback attributes in MOI.ListOfModelAttributesSet (#584)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Nov 1, 2024
1 parent d7f1459 commit b51f9c3
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 9 deletions.
30 changes: 30 additions & 0 deletions src/MOI_wrapper/MOI_callbacks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,17 @@ function MOI.set(model::Optimizer, ::CallbackFunction, f::Function)
_update_if_necessary(model)
return
end

MOI.supports(::Optimizer, ::CallbackFunction) = true

function MOI.set(model::Optimizer, ::CallbackFunction, ::Nothing)
ret = GRBsetcallbackfunc(model, C_NULL, C_NULL)
_check_ret(model, ret)
model.generic_callback = nothing
model.has_generic_callback = false
return
end

"""
load_callback_variable_primal(cb_data, cb_where)
Expand Down Expand Up @@ -187,8 +196,14 @@ function MOI.set(model::Optimizer, ::MOI.LazyConstraintCallback, cb::Function)
model.lazy_callback = cb
return
end

MOI.supports(::Optimizer, ::MOI.LazyConstraintCallback) = true

function MOI.set(model::Optimizer, ::MOI.LazyConstraintCallback, ::Nothing)
model.lazy_callback = nothing
return
end

function MOI.submit(
model::Optimizer,
cb::MOI.LazyConstraint{CallbackData},
Expand Down Expand Up @@ -223,6 +238,7 @@ function MOI.submit(
_check_ret(model, ret)
return
end

MOI.supports(::Optimizer, ::MOI.LazyConstraint{CallbackData}) = true

# ==============================================================================
Expand All @@ -233,8 +249,14 @@ function MOI.set(model::Optimizer, ::MOI.UserCutCallback, cb::Function)
model.user_cut_callback = cb
return
end

MOI.supports(::Optimizer, ::MOI.UserCutCallback) = true

function MOI.set(model::Optimizer, ::MOI.UserCutCallback, ::Nothing)
model.user_cut_callback = nothing
return
end

function MOI.submit(
model::Optimizer,
cb::MOI.UserCut{CallbackData},
Expand Down Expand Up @@ -269,6 +291,7 @@ function MOI.submit(
_check_ret(model, ret)
return
end

MOI.supports(::Optimizer, ::MOI.UserCut{CallbackData}) = true

# ==============================================================================
Expand All @@ -279,8 +302,14 @@ function MOI.set(model::Optimizer, ::MOI.HeuristicCallback, cb::Function)
model.heuristic_callback = cb
return
end

MOI.supports(::Optimizer, ::MOI.HeuristicCallback) = true

function MOI.set(model::Optimizer, ::MOI.HeuristicCallback, ::Nothing)
model.heuristic_callback = nothing
return
end

function MOI.submit(
model::Optimizer,
cb::MOI.HeuristicSolution{CallbackData},
Expand Down Expand Up @@ -310,4 +339,5 @@ function MOI.submit(
# later in the optimization process.
return MOI.HEURISTIC_SOLUTION_UNKNOWN
end

MOI.supports(::Optimizer, ::MOI.HeuristicSolution{CallbackData}) = true
22 changes: 17 additions & 5 deletions src/MOI_wrapper/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,18 @@ function MOI.get(model::Optimizer, ::MOI.ListOfModelAttributesSet)
if MOI.get(model, MOI.Name()) != ""
push!(attributes, MOI.Name())
end
if model.has_generic_callback
push!(attributes, CallbackFunction())
end
if model.lazy_callback !== nothing
push!(attributes, MOI.LazyConstraintCallback())
end
if model.user_cut_callback !== nothing
push!(attributes, MOI.UserCutCallback())
end
if model.heuristic_callback !== nothing
push!(attributes, MOI.HeuristicCallback())
end
return attributes
end

Expand Down Expand Up @@ -2702,10 +2714,10 @@ end
function MOI.optimize!(model::Optimizer)
_update_if_necessary(model, force = true)
# Initialize callbacks if necessary.
has_null_callback = false
set_temporary_callback = false
if _check_moi_callback_validity(model)
MOI.set(model, CallbackFunction(), _default_moi_callback(model))
model.has_generic_callback = false
set_temporary_callback = true
elseif model.enable_interrupts && !model.has_generic_callback
# From the docstring of disable_sigint, "External functions that do not
# call julia code or julia runtime automatically disable sigint during
Expand All @@ -2715,7 +2727,7 @@ function MOI.optimize!(model::Optimizer)
# https://github.com/JuliaLang/julia/issues/2622 --- set a null
# callback.
MOI.set(model, CallbackFunction(), (x, y) -> nothing)
has_null_callback = true
set_temporary_callback = true
end

# Catch [CTRL+C], even when Julia is run from a script not in interactive
Expand All @@ -2737,10 +2749,10 @@ function MOI.optimize!(model::Optimizer)
model.has_unbounded_ray =
MOI.get(model, MOI.PrimalStatus()) == MOI.INFEASIBILITY_CERTIFICATE

if has_null_callback
if set_temporary_callback
# See https://github.com/jump-dev/Gurobi.jl/issues/395 - avoid error
# from _check_moi_callback_validity upon next optimize! call.
model.has_generic_callback = false
MOI.set(model, CallbackFunction(), nothing)
end
return
end
Expand Down
45 changes: 41 additions & 4 deletions test/MOI/MOI_callbacks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ using Gurobi
using Random
using Test

import MathOptInterface as MOI

function runtests()
for name in names(@__MODULE__; all = true)
if startswith("$(name)", "test_")
Expand All @@ -21,8 +23,6 @@ function runtests()
return
end

const MOI = Gurobi.MOI

const GRB_ENV =
isdefined(Main, :GRB_ENV) ? Main.GRB_ENV : Gurobi.Env(output_flag = 0)

Expand Down Expand Up @@ -122,6 +122,12 @@ function test_lazy_constraint_callback()
@test lazy_called
@test MOI.get(model, MOI.VariablePrimal(), x) == 1
@test MOI.get(model, MOI.VariablePrimal(), y) == 2
attrs = MOI.get(model, MOI.ListOfModelAttributesSet())
@test MOI.LazyConstraintCallback() in attrs
MOI.set(model, MOI.LazyConstraintCallback(), nothing)
attrs = MOI.get(model, MOI.ListOfModelAttributesSet())
@test !(MOI.LazyConstraintCallback() in attrs)
return
end

function test_lazy_constraint_callback_fractional()
Expand All @@ -148,6 +154,7 @@ function test_lazy_constraint_callback_fractional()
MOI.optimize!(model)
@test lazy_called_integer
@test lazy_called_fractional
return
end

function test_lazy_constraint_callback_OptimizeInProgress()
Expand All @@ -170,7 +177,8 @@ function test_lazy_constraint_callback_OptimizeInProgress()
)
end,
)
return MOI.optimize!(model)
MOI.optimize!(model)
return
end

function test_lazy_constraint_callback_UserCut()
Expand All @@ -193,6 +201,7 @@ function test_lazy_constraint_callback_UserCut()
MOI.InvalidCallbackUsage(MOI.LazyConstraintCallback(), MOI.UserCut(cb)),
MOI.optimize!(model)
)
return
end

function test_lazy_constraint_callback_HeuristicSolution()
Expand All @@ -213,6 +222,7 @@ function test_lazy_constraint_callback_HeuristicSolution()
),
MOI.optimize!(model)
)
return
end

function test_user_cut_callback()
Expand Down Expand Up @@ -245,6 +255,12 @@ function test_user_cut_callback()
@test MOI.supports(model, MOI.UserCutCallback())
MOI.optimize!(model)
@test user_cut_submitted
attrs = MOI.get(model, MOI.ListOfModelAttributesSet())
@test MOI.UserCutCallback() in attrs
MOI.set(model, MOI.UserCutCallback(), nothing)
attrs = MOI.get(model, MOI.ListOfModelAttributesSet())
@test !(MOI.UserCutCallback() in attrs)
return
end

function test_user_cut_callback_LazyConstraint()
Expand All @@ -267,6 +283,7 @@ function test_user_cut_callback_LazyConstraint()
MOI.InvalidCallbackUsage(MOI.UserCutCallback(), MOI.LazyConstraint(cb)),
MOI.optimize!(model)
)
return
end

function test_user_cut_callback_HeuristicSolution()
Expand All @@ -287,6 +304,7 @@ function test_user_cut_callback_HeuristicSolution()
),
MOI.optimize!(model)
)
return
end

function test_heuristic_callback()
Expand Down Expand Up @@ -329,6 +347,12 @@ function test_heuristic_callback()
MOI.optimize!(model)
@test solution_accepted
@test solution_rejected
attrs = MOI.get(model, MOI.ListOfModelAttributesSet())
@test MOI.HeuristicCallback() in attrs
MOI.set(model, MOI.HeuristicCallback(), nothing)
attrs = MOI.get(model, MOI.ListOfModelAttributesSet())
@test !(MOI.HeuristicCallback() in attrs)
return
end

function test_heuristic_callback_LazyConstraint()
Expand All @@ -354,6 +378,7 @@ function test_heuristic_callback_LazyConstraint()
),
MOI.optimize!(model)
)
return
end

function test_heuristic_callback_UserCut()
Expand All @@ -376,6 +401,7 @@ function test_heuristic_callback_UserCut()
MOI.InvalidCallbackUsage(MOI.HeuristicCallback(), MOI.UserCut(cb)),
MOI.optimize!(model)
)
return
end

function test_CallbackFunction_callback_OptimizeInProgress()
Expand All @@ -399,7 +425,8 @@ function test_CallbackFunction_callback_OptimizeInProgress()
end,
)
@test MOI.supports(model, Gurobi.CallbackFunction())
return MOI.optimize!(model)
MOI.optimize!(model)
return
end

function test_CallbackFunction_callback_LazyConstraint()
Expand Down Expand Up @@ -443,6 +470,12 @@ function test_CallbackFunction_callback_LazyConstraint()
@test Gurobi.GRB_CB_MESSAGE in cb_calls
@test Gurobi.GRB_CB_PRESOLVE in cb_calls
@test Gurobi.GRB_CB_MIPSOL in cb_calls
attrs = MOI.get(model, MOI.ListOfModelAttributesSet())
@test Gurobi.CallbackFunction() in attrs
MOI.set(model, Gurobi.CallbackFunction(), nothing)
attrs = MOI.get(model, MOI.ListOfModelAttributesSet())
@test !(Gurobi.CallbackFunction() in attrs)
return
end

function test_CallbackFunction_callback_UserCut()
Expand Down Expand Up @@ -490,6 +523,7 @@ function test_CallbackFunction_callback_UserCut()
MOI.optimize!(model)
@test user_cut_submitted
@test Gurobi.GRB_CB_MIPNODE in cb_calls
return
end

function test_CallbackFunction_callback_HeuristicSolution()
Expand Down Expand Up @@ -548,6 +582,7 @@ function test_CallbackFunction_callback_HeuristicSolution()
@test solution_rejected
@test solution_unknown
@test Gurobi.GRB_CB_MIPNODE in cb_calls
return
end

function test_CallbackFunction_CallbackNodeStatus()
Expand All @@ -565,6 +600,7 @@ function test_CallbackFunction_CallbackNodeStatus()
)
MOI.optimize!(model)
@test unknown_reached
return
end

function test_CallbackFunction_broadcast()
Expand All @@ -584,6 +620,7 @@ function test_CallbackFunction_broadcast()
MOI.optimize!(model)
@test length(solutions) > 0
@test length(solutions[1]) == length(x)
return
end

end # module TestCallbacks
Expand Down

0 comments on commit b51f9c3

Please sign in to comment.