Callback Example

An example of creating and adding a callback to a ProgressiveHedging.jl run. This example is also available as the script callback_example.jl in the example directory.

In this example, we will use the same setup as in the Basic Example.

using ProgressiveHedging
import JuMP
import Ipopt

function two_stage_model(scenario_id::ScenarioID)

    model = JuMP.Model(()->Ipopt.Optimizer())
    JuMP.set_optimizer_attribute(model, "print_level", 0)
    JuMP.set_optimizer_attribute(model, "tol", 1e-12)
    JuMP.set_optimizer_attribute(model, "acceptable_tol", 1e-12)

    scen = value(scenario_id)

    ref = JuMP.@variable(model, x >= 0.0)
    stage1 = [ref]

    ref = JuMP.@variable(model, y >= 0.0)
    stage2 = [ref]

    b_s = scen == 0 ? 11.0 : 4.0
    c_s = scen == 0 ? 0.5 : 10.0

    JuMP.@constraint(model, x + y == b_s)

    JuMP.@objective(model, Min, 1.0*x + c_s*y)

    return JuMPSubproblem(model,
                          scenario_id,
                          Dict(stid(1) => stage1,
                               stid(2) => stage2)
                          )
end

scen_tree = two_stage_tree(2)

We now write a function to get called as PH executes and wrap it in the Callback type.

function my_callback(ext::Dict{Symbol,Any},
                     phd::PHData,
                     winf::ProgressiveHedging.WorkerInf,
                     niter::Int)
    # The `ext` dictionary can be used to store things between PH iterations
    if niter == 2
        ext[:message] = "This is from iteration 2!"
    elseif niter == 5
        println("Iteration 5 found the message: " * ext[:message])
    elseif niter == 10
        println("This is iteration 10!")
        # We can access the current consensus variable values
        for (xhid, xhat) in pairs(consensus_variables(phd))
            println("The value of $(name(phd,xhid)) is $(value(xhat)).")
        end
    end
    # Returning false from the callback will terminate PH.
    # Here we stop after 20 iterations.
    return niter < 20
end

my_cb = Callback(my_callback)

Several things are worth noting here:

  • All callback functions must have the signature given here
  • The ext dictionary is unique to each callback and can be used to pass information from one iteration to the next
  • Returning false from the callback will terminate PH

Now we call the solve function as before but giving it the callback object we created.

(niter, abs_res, rel_res, obj, soln_df, phd) = solve(scen_tree,
                                                     two_stage_model,
                                                     ScalarPenaltyParameter(1.0),
                                                     callbacks=[my_cb]
                                                     )
@show niter
@show abs_res
@show rel_res
@show obj
@show soln_df
Iteration 5 found the message: This is from iteration 2!
This is iteration 10!
The value of x is 4.04687501015623.
niter = 20
abs_res = 0.002441406256835066
rel_res = 0.0006100536849070564
obj = 5.750973778188833
soln_df = 3×4 DataFrame
 Row │ variable  value    stage  scenarios
     │ String    Float64  Int64  String
─────┼─────────────────────────────────────
   1 │ x         4.00195      1  0,1
   2 │ y         6.99609      2  0
   3 │ y         0.0          2  1

The callbacks can be used to implement solution heuristics and alternative termination criteria. There are two callbacks are included with ProgressiveHedging.jl that do this. variable_fixing is a heuristic that fixes variables whose values remain (approximately) the same over a set number of iterations. It is hoped that this speeds the convergence of PH. mean_deviation is an alternative termination criteria. It is a form of mean relative absolute deviation from the consensus variable value. These are both implmentations of ideas found in (Watson & Woodruff 2010).