JuMP/CPLEX: retrieve value of array-variables in callback

Dear folks,
I use JuMP (v0.21.4) and CPLEX (v0.7.3) and try to get the current variable values of sparse/dense-axis-array variables in a solver-specific callback. A quite long MWE based on the one in the documentation is given below.
Retrieving single values works, however, I then need to create the wanted structures first. After solving is finished the values can be easily obtained by

y_val = value.(model[:y])
z_val = value.(model[:z])

Is there any similar approach for callbacks? Or at least a more efficient (and/or less buggy) approach than the work-around in the example below?

using JuMP, CPLEX, SparseArrays

function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong, model, I, K)
    CPLEX.load_callback_variable_primal(cb_data, context_id)

    #THIS WORKS
    y_val = zeros(Float64, length(I))
    for i in I
        y_val[i] = callback_value(cb_data, model[:y][i])
    end
    println("y: $y_val")

    z_val = spzeros(Float64, length(I), 5)
    for i in I, k in K[i]
        z_val[i,k] = callback_value(cb_data, model[:z][i,k])
    end

    #THIS DOES NOT WORK
    #    y_val = callback_value.(cb_data, model[:y])
    #    z_val = callback_value.(cb_data, model[:z])

    return
end

function main()
    I = 1:2
    K = [[1, 4, 5], [2, 3 ,4]]
    model = direct_model(CPLEX.Optimizer())
    MOI.set(model, MOI.NumberOfThreads(), 1)
    @variable(model, y[i in I], Bin)
    @variable(model, z[i in I, k in K[i]], Bin)
    @constraint(model, sum(y) <= 1)
    @objective(model, Max, sum(y) )

    function callCallback(cb_data::CPLEX.CallbackContext, context_id::Clong)
        if context_id != CPX_CALLBACKCONTEXT_CANDIDATE
            return
        end
        ispoint_p = Ref{Cint}()
        ret = CPXcallbackcandidateispoint(cb_data, ispoint_p)
        if ret != 0 || ispoint_p[] == 0
            return  # No candidate point available or error
        end
        my_callback_function(cb_data, context_id, model, I, K)
        return
    end
    MOI.set(model, CPLEX.CallbackFunction(), callCallback)
    optimize!(model)
    println("OV: ", objective_value(model) )

    #this works
    y_val = value.(model[:y])
    z_val = value.(model[:z])
    return
end
main()

Thanks in advance. Best, mike_k

1 Like

I found a solution:

y_val = callback_value.(Ref(cb_data), model[:y])
z_val = callback_value.(Ref(cb_data), model[:z])

does the job (although I do not understand why the Ref(.) is needed).

1 Like

The Ref has nothing to do with callback_value specifically, but is a trick used together with any broadcasted functions (i.e., with the dot between name and starting parenthesis). Every argument to a broadcasted function need to be a “container” of sorts, if it is a single element, the single element is used for each of the multiple calls, if it has multiple elements, each element is used in a different call (the first element of all vectors with multiple elements is used in the first call, then the second, and so on) and the results are assembled in a collection (often a Vector) and returned (y_val and z_val in your case).

Any other single-element zero-dimensional container works, I use the tuple constructor actually:

y_val = callback_value.((cb_data,), model[:y])
z_val = callback_value.((cb_data,), model[:z])
2 Likes

I opened a PR to fix this: https://github.com/jump-dev/CPLEX.jl/pull/354

1 Like