Multi-Stage Example
An example of solving a multi-stage problem with ProgressiveHedging.jl. This example is also available as the script multistage_example.jl in the examples directory.
Using PH to solve a multi-stage problem is very similar to a two-stage problem. The only significant difference is in the creation of the scenario tree.
We will create a three-stage problem. First we import the proper packages and define the subproblem creation function
using ProgressiveHedging
import JuMP
import Ipopt
function create_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)
c = [1.0, 10.0, 0.01]
d = 7.0
a = 16.0
α = 1.0
β = 1.0
γ = 1.0
δ = 1.0
ϵ = 1.0
s1 = 8.0
s2 = 4.0
s11 = 9.0
s12 = 16.0
s21 = 5.0
s22 = 18.0
stage1 = JuMP.@variable(model, x[1:3] >= 0.0)
JuMP.@constraint(model, x[3] <= 1.0)
obj = zero(JuMP.GenericQuadExpr{Float64,JuMP.VariableRef})
JuMP.add_to_expression!(obj, sum(c.*x))
# Second stage
stage2 = Vector{JuMP.VariableRef}()
if scenario_id < scid(2)
vref = JuMP.@variable(model, y >= 0.0)
JuMP.@constraint(model, α*sum(x) + β*y >= s1)
JuMP.add_to_expression!(obj, d*y)
else
vref = JuMP.@variable(model, y >= 0.0)
JuMP.@constraint(model, α*sum(x) + β*y >= s2)
JuMP.add_to_expression!(obj, d*y)
end
push!(stage2, vref)
# Third stage
stage3 = Vector{JuMP.VariableRef}()
if scenario_id == scid(0)
vref = JuMP.@variable(model, z[1:2])
JuMP.@constraint(model, ϵ*sum(x) + γ*y + δ*sum(z) == s11)
JuMP.add_to_expression!(obj, a*sum(z[i]^2 for i in 1:2))
elseif scenario_id == scid(1)
vref = JuMP.@variable(model, z[1:2])
JuMP.@constraint(model, ϵ*sum(x) + γ*y + δ*sum(z) == s12)
JuMP.add_to_expression!(obj, a*sum(z[i]^2 for i in 1:2))
elseif scenario_id == scid(2)
vref = JuMP.@variable(model, z[1:2])
JuMP.@constraint(model, ϵ*sum(x) + γ*y + δ*sum(z) == s21)
JuMP.add_to_expression!(obj, a*sum(z[i]^2 for i in 1:2))
else
vref = JuMP.@variable(model, z[1:2])
JuMP.@constraint(model, ϵ*sum(x) + γ*y + δ*sum(z) == s22)
JuMP.add_to_expression!(obj, a*sum(z[i]^2 for i in 1:2))
end
append!(stage3, vref)
JuMP.@objective(model, Min, obj)
vdict = Dict{StageID, Vector{JuMP.VariableRef}}([stid(1) => stage1,
stid(2) => stage2,
stid(3) => stage3,
])
return JuMPSubproblem(model, scenario_id, vdict)
end
Now we create the scenario tree with two branch nodes in the second stage.
scen_tree = ScenarioTree()
branch_node_1 = add_node(scen_tree, root(scen_tree))
branch_node_2 = add_node(scen_tree, root(scen_tree))
To each branch node, we add two leaf nodes
add_leaf(scen_tree, branch_node_1, 0.375)
add_leaf(scen_tree, branch_node_1, 0.125)
add_leaf(scen_tree, branch_node_2, 0.375)
add_leaf(scen_tree, branch_node_2, 0.125)
Finally, we solve just as we normally would
(niter, abs_res, rel_res, obj, soln_df, phd) = solve(scen_tree,
create_model,
ScalarPenaltyParameter(25.0);
atol=1e-8, rtol=1e-12, max_iter=500)
@show niter
@show abs_res
@show rel_res
@show obj
@show soln_df
niter = 106 abs_res = 8.90339301554451e-9 rel_res = 1.1773081652237095e-9 obj = 178.35374844178475 soln_df = 13×4 DataFrame Row │ variable value stage scenarios │ String Float64 Int64 String ─────┼───────────────────────────────────────── 1 │ x[3] 1.0 1 0,1,2,3 2 │ x[1] 7.5625 1 0,1,2,3 3 │ x[2] 4.28483e-9 1 0,1,2,3 4 │ y 1.75 2 0,1 5 │ y 9.87429e-9 2 2,3 6 │ z[2] -0.65625 3 0 7 │ z[1] -0.65625 3 0 8 │ z[2] 2.84375 3 1 9 │ z[1] 2.84375 3 1 10 │ z[2] -1.78125 3 2 11 │ z[1] -1.78125 3 2 12 │ z[2] 4.71875 3 3 13 │ z[1] 4.71875 3 3