Hydro 1d
This tutorial was generated using Literate.jl. Download the source as a .jl
file. Download the source as a .ipynb
file.
This is a simple version of the hydro-thermal scheduling problem. The goal is # to operate one hydro-dam and two thermal plants over time in the face of inflow uncertainty.
using SDDP, HiGHS, Test, Random, Statistics
import SDDP: Inner
function test_inner_hydro_1d()
# Parameters
# Model
nstages = 4
inivol = 83.222
# Uncertainty
nscen = 10
Random.seed!(2)
inflows = max.(40 .+ 20 * randn(nscen), 0)
# Risk aversion
# EAVaR(;lambda=1.0, beta=1.0) corresponds to
# ρ = λ * E[x] + (1 - λ) * AV@R(β)[x]
# and β = 1.0 makes AV@R = E
lambda = 0.5
beta = 0.20
rho = SDDP.EAVaR(; lambda, beta)
# Solving
niters = 10
ub_step = 10
solver = HiGHS.Optimizer
# The model
function build_hydro(sp, t)
@variable(sp, 0 <= vol <= 100, SDDP.State, initial_value = inivol)
@variable(sp, 0 <= gh <= 60)
@variable(sp, 0 <= spill <= 200)
@variable(sp, 0 <= gt[i = 1:2] <= 15)
@variable(sp, 0 <= def <= 75)
@variable(sp, inflow)
@constraint(sp, sum(gt) + gh + def == 75)
@constraint(sp, vol.in + inflow - gh - spill == vol.out)
if t == 1
JuMP.fix(inflow, 0.0)
else
SDDP.parameterize(sp, inflows) do observed_inflow
JuMP.fix(inflow, observed_inflow)
return
end
end
@stageobjective(sp, spill + 5 * gt[1] + 10 * gt[2] + 50 * def)
end
# Lower bound via cuts
println("Build primal outer model")
pb = SDDP.LinearPolicyGraph(
build_hydro;
stages = nstages,
sense = :Min,
optimizer = solver,
lower_bound = 0.0,
)
println("Solving primal outer model")
SDDP.train(pb; iteration_limit = niters, risk_measure = rho)
lb = SDDP.calculate_bound(pb; risk_measure = rho)
println(" Risk-adjusted lower bound: ", round(lb; digits = 2))
# Monte-Carlo policy cost estimate; does not take risk_measure into account
simulations = SDDP.simulate(pb, 500, [:vol, :gh, :spill, :gt, :def])
objective_values =
[sum(stage[:stage_objective] for stage in sim) for sim in simulations]
μ = round(mean(objective_values); digits = 2)
ci = round(1.96 * std(objective_values) / sqrt(500); digits = 2)
println("Risk-neutral confidence interval: ", μ, " ± ", ci)
# Upper bound via inner approximation
base_Lip = 50.0
base_ub = 75.0 * base_Lip
build_Lip(t) = base_Lip * (nstages - t)
build_ub(t) = base_ub * (nstages - t)
ibf = Inner.InnerBellmanFunction(
build_Lip;
upper_bound = build_ub,
vertex_type = SDDP.SINGLE_CUT,
)
println("\nBuilding and Solving inner model for upper bounds")
pb_inner, ub = Inner.inner_dp(
build_hydro,
pb;
nstages,
sense = :Min,
optimizer = solver,
lower_bound = 0.0,
bellman_function = ibf,
risk_measures = rho,
print_level = 1,
)
@test lb <= ub
end
test_inner_hydro_1d()
Test Passed