Callbacks
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
BlockDecomposition.getspid
— Function.getspid(data::OracleSolverData)
Returns the subproblem index for which the oracle has been assigned.
BlockDecomposition.getcurcost
— Function.getcurcost(x::JuMP.Variable)
Returns the current cost of the varibale x
.
BlockDecomposition.getcurub
— Function.getcurub(x::JuMP.Variable)
Returns the current ub of the varibale x
.
BlockDecomposition.getcurlb
— Function.getcurlb(x::JuMP.Variable)
Returns the current lb of the varibale x
.
BlockDecomposition.getcurdual
— Function.getcurdual(x::JuMP.Constraint)
Returns the current dual of the constraint x
.
JuMP.setsolutionvalue
— Function.setsolutionvalue!(data::OracleSolverData, x, val::Real)
Assigns the value val
to the variable x
in the solution of the oracle solver
BlockDecomposition.setsolutionbestobjval
— Function.setsolutionbestobjval!(data::OracleSolverData, objval::Real)
Assigns the value value
to the variable x
in the solution of the oracle solver
JuMP.addsolution
— Function.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.
Attach the oracle solver
Once the oracle solver function defined, we assign it to some subproblems using the following function.
BlockDecomposition.addoracletosp!
— 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
.
In our example, for each subproblem with m
, we assign the oracle myKnapsackSolver
.
for m in data.machines
addoracletosp!(gap, m, myKnapsackSolver)
end