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

Refactor and simplify src/MOI/MOI_callbacks.jl #244

Merged
merged 6 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
274 changes: 122 additions & 152 deletions src/MOI/MOI_callbacks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

struct CallbackFunction <: MOI.AbstractCallback end

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

Check warning on line 14 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L14

Added line #L14 was not covered by tests

function MOI.set(model::Optimizer, ::CallbackFunction, ::Nothing)
if model.callback_data !== nothing
Lib.XPRSremovecboptnode(model.inner, C_NULL, C_NULL)
Expand All @@ -26,7 +28,6 @@
model.callback_data = nothing
end
model.has_generic_callback = true
# Starting with this callback to test
model.callback_data = set_callback_optnode!(
model.inner,
(cb_data) -> begin
Expand All @@ -37,7 +38,6 @@
)
return
end
MOI.supports(::Optimizer, ::CallbackFunction) = true

function get_cb_solution(model::Optimizer, model_inner::XpressProblem)
reset_callback_cached_solution(model)
Expand All @@ -51,26 +51,26 @@
return
end

function applycuts(opt::Optimizer, model::XpressProblem)
itype = Cint(1)
interp = Cint(-1) # Get all cuts
delta = 0.0#Lib.XPRS_MINUSINFINITY
ncuts = Array{Cint}(undef, 1)
size = Cint(length(opt.cb_cut_data.cutptrs))
mcutptr = Array{Lib.XPRScut}(undef, size)
dviol = Array{Cdouble}(undef, size)
Lib.XPRSgetcpcutlist(
model,
itype,
interp,
delta,
ncuts,
function _load_existing_cuts(model::Optimizer, cb_data::CallbackData)
if isempty(model.cb_cut_data.cutptrs)
return false

Check warning on line 56 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L54-L56

Added lines #L54 - L56 were not covered by tests
end
p_ncuts = Ref{Cint}(0)
size = length(model.cb_cut_data.cutptrs)
mcutptr = Vector{Lib.XPRScut}(undef, size)
dviol = Vector{Cdouble}(undef, size)
@checked Lib.XPRSgetcpcutlist(

Check warning on line 62 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L58-L62

Added lines #L58 - L62 were not covered by tests
cb_data.model,
1, # itype
-1, # interp
0.0, # delta
p_ncuts,
size,
mcutptr,
dviol,
) # requires an availabel solution
Lib.XPRSloadcuts(model, itype, interp, ncuts[1], mcutptr)
return ncuts[1] > 0
)
@checked Lib.XPRSloadcuts(cb_data.model, 1, -1, p_ncuts[], mcutptr)
return p_ncuts[] > 0

Check warning on line 73 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L72-L73

Added lines #L72 - L73 were not covered by tests
end

# ==============================================================================
Expand All @@ -82,7 +82,6 @@
get_cb_solution(model, cb_data.model)
if model.heuristic_callback !== nothing
model.callback_state = CB_HEURISTIC
# only allow one heuristic solution per LP optimal node
cb_count = @_invoke Lib.XPRSgetintattrib(
cb_data.model,
Lib.XPRS_CALLBACKCOUNT_OPTNODE,
Expand All @@ -95,16 +94,9 @@
end
if model.user_cut_callback !== nothing
model.callback_state = CB_USER_CUT
# apply stored cuts if any
if length(model.cb_cut_data.cutptrs) > 0
added = applycuts(model, cb_data.model)
if added
return
end
if _load_existing_cuts(model, cb_data)
return

Check warning on line 98 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L97-L98

Added lines #L97 - L98 were not covered by tests
end
# only allow one user cut solution per LP optimal node
# limiting two calls to guarantee th user has a chance to add
# a cut. if the user cut is loose the problem will be resolved anyway.
cb_count = @_invoke Lib.XPRSgetintattrib(
cb_data.model,
Lib.XPRS_CALLBACKCOUNT_OPTNODE,
Expand All @@ -117,14 +109,8 @@
end
if model.lazy_callback !== nothing
model.callback_state = CB_LAZY
# add previous cuts if any
# to gurantee the user is dealing with a optimal solution
# feasibile for exisitng cuts
if length(model.cb_cut_data.cutptrs) > 0
added = applycuts(model, cb_data.model)
if added
return
end
if _load_existing_cuts(model, cb_data)
return

Check warning on line 113 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L112-L113

Added lines #L112 - L113 were not covered by tests
end
model.lazy_callback(cb_data)
end
Expand All @@ -133,32 +119,49 @@
end

function MOI.get(model::Optimizer, attr::MOI.CallbackNodeStatus{CallbackData})
if check_moi_callback_validity(model)
mip_infeas = @_invoke Lib.XPRSgetintattrib(
attr.callback_data.model,
Lib.XPRS_MIPINFEAS,
_,
)::Int
if mip_infeas == 0
return MOI.CALLBACK_NODE_STATUS_INTEGER
elseif mip_infeas > 0
return MOI.CALLBACK_NODE_STATUS_FRACTIONAL
end
if !check_moi_callback_validity(model)
return MOI.CALLBACK_NODE_STATUS_UNKNOWN

Check warning on line 123 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L122-L123

Added lines #L122 - L123 were not covered by tests
end
mip_infeas = @_invoke Lib.XPRSgetintattrib(

Check warning on line 125 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L125

Added line #L125 was not covered by tests
attr.callback_data.model,
Lib.XPRS_MIPINFEAS,
_,
)::Int
if mip_infeas == 0
return MOI.CALLBACK_NODE_STATUS_INTEGER

Check warning on line 131 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L130-L131

Added lines #L130 - L131 were not covered by tests
end
return MOI.CALLBACK_NODE_STATUS_UNKNOWN
return MOI.CALLBACK_NODE_STATUS_FRACTIONAL

Check warning on line 133 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L133

Added line #L133 was not covered by tests
end

function MOI.get(
model::Optimizer,
::MOI.CallbackVariablePrimal{CallbackData},
x::MOI.VariableIndex,
)
return model.callback_cached_solution.variable_primal[_info(
model,
x,
).column]
column = _info(model, x).column
return model.callback_cached_solution.variable_primal[column]

Check warning on line 142 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L141-L142

Added lines #L141 - L142 were not covered by tests
end

function callback_exception(model::Optimizer, cb, err::Exception)
model.cb_exception = err
Lib.XPRSinterrupt(cb.callback_data.model, Lib.XPRS_STOP_USER)
return

Check warning on line 148 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L145-L148

Added lines #L145 - L148 were not covered by tests
end

function _throw_if_invalid_state(model, cb, calling_state)
if model.callback_state in (calling_state, CB_NONE, CB_GENERIC)
return

Check warning on line 153 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L151-L153

Added lines #L151 - L153 were not covered by tests
end
attr = if model.callback_state == CB_HEURISTIC
MOI.HeuristicCallback()
elseif model.callback_state == CB_LAZY
MOI.LazyConstraintCallback()

Check warning on line 158 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L155-L158

Added lines #L155 - L158 were not covered by tests
else
@assert model.callback_state == CB_USER_CUT
MOI.UserCutCallback()

Check warning on line 161 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L160-L161

Added lines #L160 - L161 were not covered by tests
end
return callback_exception(model, cb, MOI.InvalidCallbackUsage(attr, cb))

Check warning on line 163 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L163

Added line #L163 was not covered by tests
end
# ==============================================================================
# MOI.UserCutCallback & MOI.LazyConstraint
# ==============================================================================
Expand All @@ -169,95 +172,88 @@
return
end

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

Check warning on line 175 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L175

Added line #L175 was not covered by tests

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

Check warning on line 177 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L177

Added line #L177 was not covered by tests

function MOI.submit(

Check warning on line 179 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L179

Added line #L179 was not covered by tests
model::Optimizer,
cb::MOI.UserCut{CallbackData},
f::MOI.ScalarAffineFunction{Float64},
s::Union{
MOI.LessThan{Float64},
MOI.GreaterThan{Float64},
MOI.EqualTo{Float64},
},
)
model.cb_cut_data.submitted = true
_throw_if_invalid_state(model, cb, CB_USER_CUT)
indices, coefficients = _indices_and_coefficients(model, f)
sense, rhs = _sense_and_rhs(s)
mindex = Vector{Lib.XPRScut}(undef, 1)
@checked Lib.XPRSstorecuts(

Check warning on line 194 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L189-L194

Added lines #L189 - L194 were not covered by tests
cb.callback_data.model,
1, # ncuts
2, # nodupl,
Cint[1], # mtype
[sense], # sensetype,
[rhs - f.constant], # drhs
Cint[0, length(indices)], # mstart
mindex,
Cint.(indices .- 1), # mcols
coefficients,
)
@checked Lib.XPRSloadcuts(cb.callback_data.model, 1, Cint(-1), 1, mindex)
push!(model.cb_cut_data.cutptrs, mindex[1])
return

Check warning on line 208 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L206-L208

Added lines #L206 - L208 were not covered by tests
end

# ==============================================================================
# MOI.LazyConstraint
# ==============================================================================

function MOI.set(model::Optimizer, ::MOI.LazyConstraintCallback, cb::Function)
MOI.set(model, MOI.RawOptimizerAttribute("MIPDUALREDUCTIONS"), 0)
model.lazy_callback = cb
return
end

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

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

function MOI.submit(
model::Optimizer,
cb::CB,
cb::MOI.LazyConstraint{CallbackData},
f::MOI.ScalarAffineFunction{Float64},
s::Union{
MOI.LessThan{Float64},
MOI.GreaterThan{Float64},
MOI.EqualTo{Float64},
},
) where {CB<:Union{MOI.UserCut{CallbackData},MOI.LazyConstraint{CallbackData}}}
model_cb = cb.callback_data.model
)
model.cb_cut_data.submitted = true
if model.callback_state == CB_HEURISTIC
cache_exception(
model,
MOI.InvalidCallbackUsage(MOI.HeuristicCallback(), cb),
)
Lib.XPRSinterrupt(model_cb, Lib.XPRS_STOP_USER)
return
elseif model.callback_state == CB_LAZY && CB <: MOI.UserCut{CallbackData}
cache_exception(
model,
MOI.InvalidCallbackUsage(MOI.LazyConstraintCallback(), cb),
)
Lib.XPRSinterrupt(model_cb, Lib.XPRS_STOP_USER)
return
elseif model.callback_state == CB_USER_CUT &&
CB <: MOI.LazyConstraint{CallbackData}
cache_exception(
model,
MOI.InvalidCallbackUsage(MOI.UserCutCallback(), cb),
)
Lib.XPRSinterrupt(model_cb, Lib.XPRS_STOP_USER)
return
elseif !iszero(f.constant)
cache_exception(
model,
MOI.ScalarFunctionConstantNotZero{Float64,typeof(f),typeof(s)}(
f.constant,
),
)
Lib.XPRSinterrupt(model_cb, Lib.XPRS_STOP_USER)
return
end
_throw_if_invalid_state(model, cb, CB_LAZY)

Check warning on line 234 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L234

Added line #L234 was not covered by tests
indices, coefficients = _indices_and_coefficients(model, f)
sense, rhs = _sense_and_rhs(s)

mtype = Int32[1] # Cut type
mstart = Int32[0, length(indices)]
mindex = Array{Lib.XPRScut}(undef, 1)
ncuts = Cint(1)
ncuts_ptr = Cint[0]
nodupl = Cint(2) # Duplicates are excluded from the cut pool, ignoring cut type
sensetype = Cchar[Char(sense)]
drhs = Float64[rhs]
indices .-= 1
mcols = Cint.(indices)
interp = Cint(-1) # Load all cuts

ret = Lib.XPRSstorecuts(
model_cb,
ncuts,
nodupl,
Cint.(mtype),
sensetype,
drhs,
Cint.(mstart),
mindex = Vector{Lib.XPRScut}(undef, 1)
@checked Lib.XPRSstorecuts(

Check warning on line 238 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L237-L238

Added lines #L237 - L238 were not covered by tests
cb.callback_data.model,
1, # ncuts
2, # nodupl,
Cint[1], # mtype
[sense], # sensetype,
[rhs - f.constant], # drhs
Cint[0, length(indices)], # mstart
mindex,
Cint.(mcols),
Cint.(indices .- 1), # mcols
coefficients,
)
Lib.XPRSloadcuts(model_cb, mtype[], interp, ncuts, mindex)
@checked Lib.XPRSloadcuts(cb.callback_data.model, 1, Cint(-1), 1, mindex)

Check warning on line 250 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L250

Added line #L250 was not covered by tests
push!(model.cb_cut_data.cutptrs, mindex[1])
model.cb_cut_data.cutptrs
return
end

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

Check warning on line 255 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L255

Added line #L255 was not covered by tests

# ==============================================================================
# MOI.HeuristicCallback
# ==============================================================================
Expand All @@ -266,6 +262,7 @@
model.heuristic_callback = cb
return
end

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

function MOI.submit(
Expand All @@ -274,43 +271,16 @@
variables::Vector{MOI.VariableIndex},
values::MOI.Vector{Float64},
)
model_cb = cb.callback_data.model::Xpress.XpressProblem
model_cb2 = cb.callback_data.model_root::Xpress.XpressProblem
if model.callback_state == CB_LAZY
cache_exception(
model,
MOI.InvalidCallbackUsage(MOI.LazyConstraintCallback(), cb),
)
Lib.XPRSinterrupt(model_cb, Lib.XPRS_STOP_USER)
return
elseif model.callback_state == CB_USER_CUT
cache_exception(
model,
MOI.InvalidCallbackUsage(MOI.UserCutCallback(), cb),
)
Lib.XPRSinterrupt(model_cb, Lib.XPRS_STOP_USER)
return
end
ilength = length(variables)
mipsolval = fill(NaN, ilength)
mipsolcol = fill(NaN, ilength)
count = 1
for (var, value) in zip(variables, values)
mipsolcol[count] = convert(Cint, _info(model, var).column - 1)
mipsolval[count] = value
count += 1
end
mipsolcol = Cint.(mipsolcol)
mipsolval = Cdouble.(mipsolval)
if ilength == MOI.get(model, MOI.NumberOfVariables())
mipsolcol = C_NULL
_throw_if_invalid_state(model, cb, CB_HEURISTIC)
nnz = length(variables)
mipsolcol = if nnz == MOI.get(model, MOI.NumberOfVariables())
C_NULL

Check warning on line 277 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L274-L277

Added lines #L274 - L277 were not covered by tests
else
Cint[_info(model, x).column - 1 for x in variables]

Check warning on line 279 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L279

Added line #L279 was not covered by tests
end
@checked Lib.XPRSaddmipsol(model_cb, ilength, mipsolval, mipsolcol, C_NULL)
model_cb = cb.callback_data.model::XpressProblem
@checked Lib.XPRSaddmipsol(model_cb, nnz, values, mipsolcol, C_NULL)

Check warning on line 282 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L281-L282

Added lines #L281 - L282 were not covered by tests
return MOI.HEURISTIC_SOLUTION_UNKNOWN
end
MOI.supports(::Optimizer, ::MOI.HeuristicSolution{CallbackData}) = true

function cache_exception(model::Optimizer, e::Exception)
model.cb_exception = e
return
end
MOI.supports(::Optimizer, ::MOI.HeuristicSolution{CallbackData}) = true

Check warning on line 286 in src/MOI/MOI_callbacks.jl

View check run for this annotation

Codecov / codecov/patch

src/MOI/MOI_callbacks.jl#L286

Added line #L286 was not covered by tests
Loading
Loading