JKHA
JKHA

Reputation: 1896

JuMP - Having both Heuristic and LazyConstraints Callbacks on integer nodes

I am sorry, this time I could not produce a MWE. I tried but could not.

In JuMP Julia, I wanted to know if it is possible to get both Heuristic and LazyConstraints Callbacks on integer nodes. On the following NOT MWE code:

    function call_back_benders_two_opt(cb_data)
    status = callback_node_status(cb_data, m)
    if status != MOI.CALLBACK_NODE_STATUS_FRACTIONAL
        @show status
    end
    if status == MOI.CALLBACK_NODE_STATUS_INTEGER
        x_cb = zeros(Bool, n, n)
        for i in 1:n
            for j in i+1:n
                x_cb[i,j] = Bool(round(callback_value(cb_data, x[i, j])))
            end
        end
        if sum(x_cb) > 0
            ŷ = [Bool(round(callback_value(cb_data, y[i,i]))) for i in 1:n]
            ring_edges = create_ring_edges_lazy(cb_data, x, n)
            is_valid = validate_solution_lazy(ring_edges, ŷ, n)
            if is_valid
                x′, y_sp, sp_cost = compute_sp_res(x_cb, ŷ, V, n, tildeV, r, s)
                hubs, master_cost = compute_master_res(x_cb, ŷ, V, n, tildeV, o, r)
                x_opt = run_two_opt_wiki(x_cb, x′, hubs, sp_cost+master_cost, pars, n, r, rp, tildeV)[1]
                for i in V
                        status = MOI.submit(
                        m, MOI.HeuristicSolution(cb_data), [x[i,j] for j in i+1:n], [x_opt[i,j] for j in i+1:n]
                        )
                end
                println("I submitted a heuristic solution, and the status was: ", status)
            end
        end
    end

    MOI.set(m, MOI.HeuristicCallback(), call_back_benders_two_opt)
    MOI.set(m, MOI.LazyConstraintCallback(), call_back_benders)

My problem is that the Heuristic Callback is only called on Fractional nodes. Indeed line @show status never prints meaning that status is always MOI.CALLBACK_NODE_STATUS_FRACTIONAL while I wanted to call my HeuristicCallback on Integer nodes. Please note that on Integer Nodes, I do successfully add Lazy Constraints. So my concern is having both Heuristic Callback AND Lazy Constraints called on integer nodes. Is there something I am missing?

Please note that I use both Heuristic Callbacks and Lazy Constraints from this Julia Doc page.

EDIT: Thanks to @mattmilten's answer, I am editing my post. I then tried to put the Heuristic Callbacks inside the Lazy Constraints Callback as you can not have two callbacks in JuMP.

    function call_back_benders(cb_data)

    ŷ = [Bool(round(callback_value(cb_data, y[i,i]))) for i in 1:n]

    start_time_sp = time()
    for i in 1:n
        for j in i+1:n
            x̂[i,j] = Bool(round(callback_value(cb_data, x[i, j])))
        end
    end

    ring_edges = create_ring_edges_lazy(cb_data, x, n)
    is_valid = validate_solution_lazy(ring_edges, ŷ, n)
    if !is_valid
        nsubtour_cons = create_subtour_constraint_lazy(m, cb_data, x, y, n, ring_edges, nsubtour_cons)
    else

        λ0 = callback_value(cb_data, λ)
        start_time_sp = time()
        if pars.sp_solve == "poly"
            λ_val, α, γ, β, δ = sp_optimize_poly(ŷ, x̂, V, tildeV, rp, s, pars.sp_solve)
        else
            λ_val, α, γ, β, δ = sp_optimize_ilp_dual(ŷ, x̂, V, tildeV, rp, s, pars.log_level, gurobi_env)
        end
        sp_time += time() - start_time_sp

        if λ0 < λ_val
            RHS = sum([2(1-y[i,i])α[i] for i in V]) +
          sum([(x[mima(β[j][1],j)] + x[mima(β[j][2],j)] - 1)rp[mima(β[j][1],β[j][2])]
                                        for j in tildeV if length(β[j]) > 0]) -
          sum([y[j,j]γ[i,j] for i in V, j in V if i != j])

            pars.log_level > 1 && @info "Optimality cut found"
            nopt_cons += 1
            con = @build_constraint(λ ≥ RHS)

            MOI.submit(m, MOI.LazyConstraint(cb_data), con)





            ################ 2-opt

            status = callback_node_status(cb_data, m)
            if status != MOI.CALLBACK_NODE_STATUS_FRACTIONAL
                @show status
            end
            if status == MOI.CALLBACK_NODE_STATUS_INTEGER
                if sum(x̂) > 0
                    x′, y_sp, sp_cost = compute_sp_res(x̂, ŷ, V, n, tildeV, r, s)
                    hubs, master_cost = compute_master_res(x̂, ŷ, V, n, tildeV, o, r)
                    x_opt = run_two_opt_wiki(x̂, x′, hubs, sp_cost+master_cost, pars, n, r, rp, tildeV)[1]
                    for i in V
                        status = MOI.submit(
                        m, MOI.HeuristicSolution(cb_data), [x[i,j] for j in i+1:n], [x_opt[i,j] for j in i+1:n]
                        )
                    end
                    println("I submitted a heuristic solution, and the status was: ", status)
                end
            end
        end
    end

MOI.set(m, MOI.LazyConstraintCallback(), call_back_benders)

However, Julia tells me this is an invalid Callback Usage:

ERROR: InvalidCallbackUsage: Cannot submit MathOptInterface.HeuristicSolution{Gurobi.CallbackData}(Gurobi.CallbackData(    sense  : minimize
number of variables             = 14952
number of linear constraints    = 103
number of quadratic constraints = 0
number of sos constraints       = 0
number of non-zero coeffs       = 14952
number of non-zero qp objective terms  = 0
number of non-zero qp constraint terms = 0
, Ptr{Nothing} @0x000055755c429d40, 4)) inside a 
MathOptInterface.LazyConstraintCallback().

Does it mean I can not use solver-independent callback from JuMP?

Upvotes: 0

Views: 324

Answers (1)

mattmilten
mattmilten

Reputation: 6706

In Gurobi, only one callback function can be passed to the solver. Inside this function, you can define multiple actions for the different scenarios by separating them via the where argument, as demonstrated in this Python example:

def mycallback(model, where):
    if where == GRB.Callback.PRESOLVE:
        # Presolve callback
        cdels = model.cbGet(GRB.Callback.PRE_COLDEL)
        rdels = model.cbGet(GRB.Callback.PRE_ROWDEL)
        if cdels or rdels:
            print(f"{cdel} columns and {rdel} rows are removed")
    elif where == GRB.Callback.SIMPLEX:
        # Simplex callback
        itcnt = model.cbGet(GRB.Callback.SPX_ITRCNT)
        print(f"{itcnt} simplex iterations")
    elif where == GRB.Callback.MIP:
        # General MIP callback
        pass
    elif where == GRB.Callback.MIPSOL:
        # MIP solution callback
        pass
    elif where == GRB.Callback.MIPNODE:
        # MIP node callback
        pass

This should also be possible with JuMP but you need to define the callbacks yourself and combine them into a single function. The JuMP documentation explicitly warns that only one callback can be defined using the set function:

You can only set each callback once. Calling set twice will over-write the earlier callback. In addition, if you use a solver-independent callback, you cannot set a solver-dependent callback.

Upvotes: 1

Related Questions