Jump.jl: [docs] Callbacks

Created on 16 Oct 2018  路  4Comments  路  Source: jump-dev/JuMP.jl

I am getting often questions about why generic callbacks were dropped from JuMP. I think we need to have in the doc an example that shows the solver dependent aspect of callbacks and the inconsistency issues it can raise.

Also I wonder if we can restrict the scope of that dependancy.

For example instead of the old:

function user_routine(cb::MathProgBase.MathProgCallbackData)
    x_val = getvalue(x)
    y_val = getvalue(y)
    ...
    @lazyconstraint(cb, y - x <= 1)
    ...
end

# Tell JuMP/Cplex to use our callback function
addlazycallback(m, user_routing)

we can imagine something like:

struct Cplex.LazyConstraintCallback <: MOI.ConstraintGenerationCallback
...
end

struct Cplex.LazyConstraintCallbackData <: MOI.ConstraintGenerationCallbackData 
...
end

function user_routine(cb)
    x_val = getvalue(x)
    y_val = getvalue(y)
    ...
    @generated_constraint(cb, y - x <= 1)
    ...
end

# Tell JuMP/Cplex to use our callback function
add_constraint_generation_callback(m, CPLEX.LazyConstraintCallback, user_routine)

While add_constraint_generation_callback will work for any constraint generation callback ( lazy constraint, a user cut, ... )

Even if we don't develop it right now, pointing on how the new callbacks might look like in a year or so, may encourage people who are still hesitant to switch to JuMP.

docs

Most helpful comment

Here are lazy constraints in Gurobi at the MOI level
https://github.com/JuliaOpt/Gurobi.jl/blob/a5d96f42bc51f9771a149a3c6a9fa15910a28665/src/MOIWrapper.jl#L491-L529
and an example
https://github.com/JuliaOpt/Gurobi.jl/blob/a5d96f42bc51f9771a149a3c6a9fa15910a28665/test/MOIWrapper.jl#L126-L192

At the JuMP level, you could imagine a syntax like the following:
@function_in_set (to be bikeshed) would parse a constraint into an MOI function and MOI set.

model = Model(with_optimizer(Gurobi.Optimizer))
@variable(model, 0 <= x <= 2, Int)
@variable(model, 0 <= y <= 2, Int)
@objective(model, Max, y)
MOI.set(model, Gurobi.CallbackFunction() do cb_data, cb_where
    Gurobi.loadcbsolution!(model, cb_data, cb_where)
    x_val = JuMP.result_value(x)
    y_val = JuMP.result_value(y)
    # We have two constraints, one cutting off the top
    # left corner and one cutting off the top right corner, e.g.
    # (0,2) +---+---+ (2,2)
    #       |xx/ \xx|
    #       |x/   \x|
    #       |/     \|
    # (0,1) +       + (2,1)
    #       |       |
    # (0,0) +---+---+ (2,0)
    TOL = 1e-6  # Allow for some impreciseness in the solution
    if y_val - x_val > 1 + TOL
        Gurobi.cblazy!(cb_data, model, JuMP.@function_in_set(y <= 1 + x))
    elseif y_val + x_val > 3 + TOL
        Gurobi.cblazy!(cb_data, model, JuMP.@function_in_set(y <= 3 - x))
    end

end)

All 4 comments

The docs are an appropriate place to present a roadmap of our plans for callbacks in JuMP. A long-form rant on why we dropped a feature from 0.18 to 0.19 would be more appropriate for a discourse post.

This framework looks general and straightforward. For callbacks that do not generate constraints, for example, the heuristic callback, there can be something similar like:

add_heuristic_callback(m, CPLEX.HeuristicCallback, user_defined_heuristic_soln)

Another method is to have a more general function to include all types of callbacks:

add_callback(m, CPLEX.SomeCallback, user_routine)

where CPLEX.SomeCallback can be a cut callback or a heuristic callback or some other types of callbacks.

Here are lazy constraints in Gurobi at the MOI level
https://github.com/JuliaOpt/Gurobi.jl/blob/a5d96f42bc51f9771a149a3c6a9fa15910a28665/src/MOIWrapper.jl#L491-L529
and an example
https://github.com/JuliaOpt/Gurobi.jl/blob/a5d96f42bc51f9771a149a3c6a9fa15910a28665/test/MOIWrapper.jl#L126-L192

At the JuMP level, you could imagine a syntax like the following:
@function_in_set (to be bikeshed) would parse a constraint into an MOI function and MOI set.

model = Model(with_optimizer(Gurobi.Optimizer))
@variable(model, 0 <= x <= 2, Int)
@variable(model, 0 <= y <= 2, Int)
@objective(model, Max, y)
MOI.set(model, Gurobi.CallbackFunction() do cb_data, cb_where
    Gurobi.loadcbsolution!(model, cb_data, cb_where)
    x_val = JuMP.result_value(x)
    y_val = JuMP.result_value(y)
    # We have two constraints, one cutting off the top
    # left corner and one cutting off the top right corner, e.g.
    # (0,2) +---+---+ (2,2)
    #       |xx/ \xx|
    #       |x/   \x|
    #       |/     \|
    # (0,1) +       + (2,1)
    #       |       |
    # (0,0) +---+---+ (2,0)
    TOL = 1e-6  # Allow for some impreciseness in the solution
    if y_val - x_val > 1 + TOL
        Gurobi.cblazy!(cb_data, model, JuMP.@function_in_set(y <= 1 + x))
    elseif y_val + x_val > 3 + TOL
        Gurobi.cblazy!(cb_data, model, JuMP.@function_in_set(y <= 3 - x))
    end

end)

@function_in_set is equivalent to the old @LinearConstraint: https://github.com/JuliaOpt/JuMP.jl/issues/710#issuecomment-436862411

Was this page helpful?
0 / 5 - 0 ratings

Related issues

remi-garcia picture remi-garcia  路  4Comments

chriscoey picture chriscoey  路  3Comments

matbesancon picture matbesancon  路  5Comments

joaquimg picture joaquimg  路  8Comments

UserQuestions picture UserQuestions  路  10Comments