Skip to content

Commit 0cc03ba

Browse files
authored
Merge pull request #117 from JuliaOpt/quad-regularization-repack
Quad regularization repack + add quadratic regularization (following T.Asamov work) + refactor run_sddp to better identify each step of the algorithm
2 parents c189bd6 + ae519cf commit 0cc03ba

File tree

10 files changed

+551
-306
lines changed

10 files changed

+551
-306
lines changed

src/SDDPoptimize.jl

Lines changed: 86 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,11 @@ fulfilled.
3232
* `problems::Array{JuMP.Model}`:
3333
the collection of linear problems used to approximate
3434
each value function
35-
* `count_callsolver::Int64`:
36-
number of times the solver has been called
35+
* `sddp_stats::SDDPStat`:
3736
3837
"""
3938
function solve_SDDP(model::SPModel, param::SDDPparameters, verbose=0::Int64)
40-
if model.IS_SMIP && isa(param.MIPSOLVER, Void)
41-
error("MIP solver is not defined. Please set `param.MIPSOLVER`")
42-
end
39+
check_SDDPparameters(model,param,verbose)
4340
# initialize value functions:
4441
V, problems = initialize_value_functions(model, param)
4542
(verbose > 0) && println("Initial value function loaded into memory.")
@@ -48,164 +45,145 @@ function solve_SDDP(model::SPModel, param::SDDPparameters, verbose=0::Int64)
4845
return V, problems, sddp_stats
4946
end
5047

48+
"""
49+
Solve SDDP algorithm with hotstart and return estimation of bellman functions.
50+
51+
# Description
52+
Alternate forward and backward phase till the stopping criterion is
53+
fulfilled.
5154
55+
# Arguments
56+
* `model::SPmodel`:
57+
the stochastic problem we want to optimize
58+
* `param::SDDPparameters`:
59+
the parameters of the SDDP algorithm
60+
* `V::Vector{PolyhedralFunction}`:
61+
current lower approximation of Bellman functions
62+
* `verbose::Int64`:
63+
Default is `0`
64+
If non null, display progression in terminal every
65+
`n` iterations, where `n` is the number specified by display.
66+
67+
# Returns
68+
* `V::Array{PolyhedralFunction}`:
69+
the collection of approximation of the bellman functions
70+
* `problems::Array{JuMP.Model}`:
71+
the collection of linear problems used to approximate
72+
each value function
73+
* `sddp_stats::SDDPStat`:
74+
"""
5275
function solve_SDDP(model::SPModel, param::SDDPparameters, V::Vector{PolyhedralFunction}, verbose=0::Int64)
53-
if model.IS_SMIP && isa(param.MIPSOLVER, Void)
54-
error("MIP solver is not defined. Please set `param.MIPSOLVER`")
55-
end
76+
check_SDDPparameters(model,param,verbose)
5677
# First step: process value functions if hotstart is called
5778
problems = hotstart_SDDP(model, param, V)
5879
sddp_stats = run_SDDP!(model, param, V, problems, verbose)
5980
return V, problems, sddp_stats
6081
end
6182

6283

63-
"""Run SDDP iterations."""
84+
"""Run SDDP iterations.
85+
86+
# Arguments
87+
* `model::SPmodel`:
88+
the stochastic problem we want to optimize
89+
* `param::SDDPparameters`:
90+
the parameters of the SDDP algorithm
91+
* `V::Vector{PolyhedralFunction}`:
92+
Polyhedral lower approximation of Bellman functions
93+
* `problems::Vector{JuMP.Model}`:
94+
* `verbose::Int64`:
95+
Default is `0`
96+
If non null, display progression in terminal every
97+
`n` iterations, where `n` is the number specified by display.
98+
99+
# Returns
100+
* `stats:SDDPStats`:
101+
contains statistics of the current algorithm
102+
"""
64103
function run_SDDP!(model::SPModel,
65104
param::SDDPparameters,
66105
V::Vector{PolyhedralFunction},
67106
problems::Vector{JuMP.Model},
68107
verbose=0::Int64)
69108

70109
#Initialization of the counter
71-
stats = SDDPStat(0, [], [], [], 0)
110+
stats = SDDPStat()
72111

73112
(verbose > 0) && println("Initialize cuts")
74113

75114
# If computation of upper-bound is needed, a set of scenarios is built
76115
# to keep always the same realization for upper bound estimation:
77-
if param.compute_upper_bound > 0
78-
upperbound_scenarios = simulate_scenarios(model.noises, param.monteCarloSize)
79-
end
116+
#if param.compute_ub > 0 #TODO
117+
upperbound_scenarios = simulate_scenarios(model.noises, param.in_iter_mc)
80118

81119
upb = Inf
82120
costs = nothing
83121
stopping_test::Bool = false
84-
iteration_count::Int64 = 0
122+
85123

86124
# Launch execution of forward and backward passes:
87-
while (iteration_count < param.maxItNumber) & (~stopping_test)
125+
while (~stopping_test)
88126
# Time execution of current pass:
89127
tic()
90128

91-
# Draw a set of scenarios according to the probability
92-
# law specified in model.noises:
93-
noise_scenarios = simulate_scenarios(model.noises, param.forwardPassNumber)
94-
95129
####################
96-
# Forward pass
97-
_, stockTrajectories,_,callsolver_forward = forward_simulations(model,
98-
param,
99-
problems,
100-
noise_scenarios)
130+
# Forward pass : compute stockTrajectories
131+
costs, stockTrajectories, callsolver_forward = forward_pass!(model, param, V, problems)
101132

102133
####################
103-
# Backward pass
104-
callsolver_backward = backward_pass!(model,
105-
param,
106-
V,
107-
problems,
108-
stockTrajectories,
109-
model.noises)
110-
111-
# Update the number of solver call
112-
stats.ncallsolver += callsolver_forward + callsolver_backward
113-
iteration_count += 1
114-
stats.niterations += 1
134+
# Backward pass : update polyhedral approximation of Bellman functions
135+
callsolver_backward = backward_pass!(model, param, V, problems, stockTrajectories, model.noises)
115136

116137
####################
117-
# If specified, prune cuts:
118-
if (param.compute_cuts_pruning > 0) && (iteration_count%param.compute_cuts_pruning==0)
119-
(verbose > 0) && println("Prune cuts ...")
120-
remove_redundant_cuts!(V)
121-
prune_cuts!(model, param, V)
122-
problems = hotstart_SDDP(model, param, V)
123-
end
124-
125-
# Update estimation of lower bound:
126-
lwb = get_bellman_value(model, param, 1, V[1], model.initialState)
127-
push!(stats.lower_bounds, lwb)
138+
# cut pruning
139+
prune_cuts!(model,param,V,stats.niterations,verbose)
128140

129141
####################
130-
# If specified, compute upper-bound:
131-
if (param.compute_upper_bound > 0) && (iteration_count%param.compute_upper_bound==0)
132-
(verbose > 0) && println("Compute upper-bound with ",
133-
param.monteCarloSize, " scenarios...")
134-
# estimate upper-bound with Monte-Carlo estimation:
135-
upb, costs = estimate_upper_bound(model, param, upperbound_scenarios, problems)
136-
if param.gap > 0.
137-
stopping_test = test_stopping_criterion(lwb, upb, param.gap)
138-
end
139-
end
142+
# In iteration upper bound estimation
143+
upb = in_iteration_upb_estimation(model, param, stats.niterations, verbose,
144+
upperbound_scenarios, upb, problems)
140145

141-
push!(stats.exectime, toq())
142-
push!(stats.upper_bounds, upb)
146+
####################
147+
# Update stats
148+
lwb = get_bellman_value(model, param, 1, V[1], model.initialState)
149+
updateSDDPStat!(stats, callsolver_forward+callsolver_backward, lwb, upb, toq())
143150

144-
if (verbose > 0) && (iteration_count%verbose==0)
145-
print("Pass number ", iteration_count)
146-
(upb < Inf) && print("\tUpper-bound: ", upb)
147-
println("\tLower-bound: ", round(stats.lower_bounds[end], 4),
148-
"\tTime: ", round(stats.exectime[end], 2),"s")
149-
end
151+
print_current_stats(stats,verbose)
150152

153+
####################
154+
# Stopping test
155+
stopping_test = test_stopping_criterion(param,stats)
156+
stats.niterations += 1
151157
end
152158

153159
##########
154160
# Estimate final upper bound with param.monteCarloSize simulations:
155-
if (verbose>0) && (param.compute_upper_bound >= 0)
156-
V0 = get_bellman_value(model, param, 1, V[1], model.initialState)
161+
display_final_solution(model, param,V,problems,stats,verbose)
162+
return stats
163+
end
164+
157165

158-
if param.compute_upper_bound == 0
166+
"""Display final results once SDDP iterations are finished."""
167+
function display_final_solution(model::SPModel, param::SDDPparameters, V, problems, stats::SDDPStat, verbose::Int64)
168+
if (verbose>0) && (param.compute_ub >= 0)
169+
lwb = get_bellman_value(model, param, 1, V[1], model.initialState)
170+
171+
if param.compute_ub == 0
159172
println("Estimate upper-bound with Monte-Carlo ...")
160-
upb, costs = estimate_upper_bound(model, param, V, problems)
173+
upb, costs = estimate_upper_bound(model, param, V, problems, param.monteCarloSize)
174+
else
175+
upb = stats.upperbounds[end]
161176
end
162177

163178
println("Estimation of upper-bound: ", round(upb,4),
164-
"\tExact lower bound: ", round(V0,4),
165-
"\t Gap < ", round(100*(upb-V0)/V0, 2) , "\% with prob. > 97.5 \%")
179+
"\tExact lower bound: ", round(lwb,4),
180+
"\t Gap < ", round(100*(upb-lwb)/lwb, 2) , "\% with prob. > 97.5 \%")
166181
println("Estimation of cost of the solution (fiability 95\%):",
167182
round(mean(costs),4), " +/- ", round(1.96*std(costs)/sqrt(length(costs)),4))
168183
end
169-
170-
return stats
171184
end
172185

173186

174-
"""
175-
Estimate upper bound with Monte Carlo.
176-
177-
# Arguments
178-
* `model::SPmodel`:
179-
the stochastic problem we want to optimize
180-
* `param::SDDPparameters`:
181-
the parameters of the SDDP algorithm
182-
* `V::Array{PolyhedralFunction}`:
183-
the current estimation of Bellman's functions
184-
* `problems::Array{JuMP.Model}`:
185-
Linear model used to approximate each value function
186-
* `n_simulation::Float64`:
187-
Number of scenarios to use to compute Monte-Carlo estimation
188-
189-
# Return
190-
* `upb::Float64`:
191-
estimation of upper bound
192-
* `costs::Vector{Float64}`:
193-
Costs along different trajectories
194-
"""
195-
function estimate_upper_bound(model::SPModel, param::SDDPparameters,
196-
V::Vector{PolyhedralFunction},
197-
problem::Vector{JuMP.Model},
198-
n_simulation=1000::Int)
199-
aleas = simulate_scenarios(model.noises, n_simulation)
200-
return estimate_upper_bound(model, param, aleas, problem)
201-
end
202-
function estimate_upper_bound(model::SPModel, param::SDDPparameters,
203-
aleas::Array{Float64, 3},
204-
problem::Vector{JuMP.Model})
205-
costs = forward_simulations(model, param, problem, aleas)[1]
206-
return upper_bound(costs), costs
207-
end
208-
209187

210188
"""
211189
Build a cut approximating terminal cost with null function
@@ -485,81 +463,3 @@ function add_cuts_to_model!(model::SPModel, t::Int64, problem::JuMP.Model, V::Po
485463
@constraint(problem, V.betas[i] + dot(lambda, xf) <= alpha)
486464
end
487465
end
488-
489-
490-
"""
491-
Exact pruning of all polyhedral functions in input array.
492-
493-
# Arguments
494-
* `model::SPModel`:
495-
* `params::SDDPparameters`:
496-
* `Vector{PolyhedralFunction}`:
497-
Polyhedral functions where cuts will be removed
498-
"""
499-
function prune_cuts!(model::SPModel, params::SDDPparameters, V::Vector{PolyhedralFunction})
500-
for i in 1:length(V)-1
501-
V[i] = exact_prune_cuts(model, params, V[i])
502-
end
503-
end
504-
505-
506-
"""
507-
Remove useless cuts in PolyhedralFunction.
508-
509-
# Arguments
510-
* `model::SPModel`:
511-
* `params::SDDPparameters`:
512-
* `V::PolyhedralFunction`:
513-
Polyhedral function where cuts will be removed
514-
515-
# Return
516-
* `PolyhedralFunction`: pruned polyhedral function
517-
"""
518-
function exact_prune_cuts(model::SPModel, params::SDDPparameters, V::PolyhedralFunction)
519-
ncuts = V.numCuts
520-
# Find all active cuts:
521-
if ncuts > 1
522-
active_cuts = Bool[is_cut_relevant(model, i, V, params.SOLVER) for i=1:ncuts]
523-
return PolyhedralFunction(V.betas[active_cuts], V.lambdas[active_cuts, :], sum(active_cuts))
524-
else
525-
return V
526-
end
527-
end
528-
529-
530-
"""
531-
Test whether the cut number k is relevant to define polyhedral function Vt.
532-
533-
# Arguments
534-
* `model::SPModel`:
535-
* `k::Int`:
536-
Position of cut to test in PolyhedralFunction object
537-
* `Vt::PolyhedralFunction`:
538-
Object storing all cuts
539-
* `solver`:
540-
Solver to use to solve linear problem
541-
* `epsilon::Float64`: default is `1e-5`
542-
Acceptable tolerance to test cuts relevantness
543-
544-
# Return
545-
* `Bool`: true if the cut is useful in the definition, false otherwise
546-
"""
547-
function is_cut_relevant(model::SPModel, k::Int, Vt::PolyhedralFunction, solver; epsilon=1e-5)
548-
m = Model(solver=solver)
549-
@variable(m, alpha)
550-
@variable(m, model.xlim[i][1] <= x[i=1:model.dimStates] <= model.xlim[i][2])
551-
552-
for i in 1:Vt.numCuts
553-
if i!=k
554-
lambda = vec(Vt.lambdas[i, :])
555-
@constraint(m, Vt.betas[i] + dot(lambda, x) <= alpha)
556-
end
557-
end
558-
559-
λ_k = vec(Vt.lambdas[k, :])
560-
@objective(m, Min, alpha - dot(λ_k, x) - Vt.betas[k])
561-
solve(m)
562-
sol = getobjectivevalue(m)
563-
return sol < epsilon
564-
end
565-

src/StochDynamicProgramming.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ include("SDDPoptimize.jl")
2929
include("extensiveFormulation.jl")
3030
include("SDPoptimize.jl")
3131
include("compare.jl")
32+
include("cutpruning.jl")
33+
include("stoppingtest.jl")
3234
end

0 commit comments

Comments
 (0)