Callbacks

Callbacks

Note

No solver over Julia supports this feature yet.

Oracles are customized solvers that can be used to solve efficiently subproblems. We introduce them using the example of Generalized Assignment Problem.

Introduction

Consider a set of machines Machines = 1:M and a set of jobs Jobs = 1:J. A machine m has a resource capacity Capacity[m]. When we assign a job j to a machine m, the job has a cost Cost[m,j] and consumes Weight[m,j] resources of the machine m. The goal is to minimize the jobs cost sum by assigning each job to a machine while not exceeding the capacity of each machine. The model is

gap = BlockModel(solver = solver)

@variable(gap, x[m in Machines, j in Jobs], Bin)

@constraint(gap, cov[0, j in Jobs],
               sum(x[m,j] for m in Machines) >= 1)

@constraint(gap, knp[m in Machines],
               sum(Weight[m,j]*x[m,j] for j in Jobs) <= Capacity[m])

@objective(gap, Min,
               sum(Cost[m,j]*x[m,j] for m in Machines, j in Jobs))

function dw_fct(cstrname::Symbol, cstrid::Tuple) :: Tuple{Symbol, Tuple}
    if cstrname == :cov           # cov constraints will be assigned in the
        return (:DW_MASTER, (0,)) # master that has the index 0
    else                          # others constraints will be assigned in a
        return (:DW_SP, cstrid)   # subproblem with same index as the constraint
    end
end
add_Dantzig_Wolfe_decomposition(gap, dw_fct)

Generalized Assignment problem can be solved using a Dantzig-Wolfe decomposition. Imagine we have a julia function that can solve efficiently the knapsack problem and returns the solution and the value of the solution

(sol,value) = solveKnp(costs::Vector{Float64}, weights::Vector{Integer}, capacity::Integer)

Write the oracle solver

We define an oracle that calls this function and solves each knapsack subproblem. ::

function myKnapsackSolver(od::OracleSolverData)
    machine = getspid(od)[0] # get the machine index
    costs = [getcurcost(x[machine,j]) for j in Jobs] # get the current cost
    (sol_x_m, value) = solveKnp(costs, Weight[m,:], Capacity[m]) # call the solver

    # Building the oracle solution
    for j in data.jobs
        # add to oracle solution variables x[machine,j] with values sol_x_m[j]
        setsolutionvalue(od, x[machine,j], sol_x_m[j])
    end

    # Set the objective value of the solution
    setsolutionbestobjval(od, value)
end
getspid(data::OracleSolverData)

Returns the subproblem index for which the oracle has been assigned.

source
getcurcost(x::JuMP.Variable)

Returns the current cost of the varibale x.

source
getcurub(x::JuMP.Variable)

Returns the current ub of the varibale x.

source
getcurlb(x::JuMP.Variable)

Returns the current lb of the varibale x.

source
getcurdual(x::JuMP.Constraint)

Returns the current dual of the constraint x.

source
JuMP.setsolutionvalueFunction.
setsolutionvalue!(data::OracleSolverData, x, val::Real)

Assigns the value val to the variable x in the solution of the oracle solver

source
setsolutionbestobjval!(data::OracleSolverData, objval::Real)

Assigns the value value to the variable x in the solution of the oracle solver

source
JuMP.addsolutionFunction.
addsolution(data::OracleSolverData)

It ends the current solution and create a new solution in the oracle solver solution. Note that the previous solutions cannot be modified anymore.

source

Attach the oracle solver

Once the oracle solver function defined, we assign it to some subproblems using the following function.

addoracletosp!(model::JuMP.Model, sp_id, sp_type::Symbol, f::Function)

Attaches the oracle f to the subproblem of type sp_type which has the index spid. The argument spid must be a Tuple or an Integer.

source

In our example, for each subproblem with m, we assign the oracle myKnapsackSolver.

for m in data.machines
    addoracletosp!(gap, m, myKnapsackSolver)
end