Skip to content

Commit d3ffbab

Browse files
committed
Merge branch 'territory-heuristic' into cuts-removal
Conflicts: src/SDDPoptimize.jl src/objects.jl test/sddp.jl
2 parents 0cc03ba + a2fbfb1 commit d3ffbab

File tree

4 files changed

+219
-35
lines changed

4 files changed

+219
-35
lines changed

src/SDDPoptimize.jl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ function run_SDDP!(model::SPModel,
108108

109109
#Initialization of the counter
110110
stats = SDDPStat()
111-
111+
territory = (param.pruning[:type]=="territory")? [Territories(model.dimStates) for i in 1:model.stageNumber-1]: nothing
112112
(verbose > 0) && println("Initialize cuts")
113113

114114
# If computation of upper-bound is needed, a set of scenarios is built
@@ -136,7 +136,15 @@ function run_SDDP!(model::SPModel,
136136

137137
####################
138138
# cut pruning
139-
prune_cuts!(model,param,V,stats.niterations,verbose)
139+
prune_cuts!(model, param, V, stockTrajectories, territory, stats.niterations, verbose)
140+
####################
141+
# Cut pruning
142+
#= prune_cuts!(model, param, V, stockTrajectories, territory, iteration_count, verbose) =#
143+
#= if (param.pruning[:period] > 0) && (iteration_count%param.pruning[:period]==0) =#
144+
#= problems = hotstart_SDDP(model, param, V) =#
145+
#= end =#
146+
147+
140148

141149
####################
142150
# In iteration upper bound estimation
@@ -149,7 +157,6 @@ function run_SDDP!(model::SPModel,
149157
updateSDDPStat!(stats, callsolver_forward+callsolver_backward, lwb, upb, toq())
150158

151159
print_current_stats(stats,verbose)
152-
153160
####################
154161
# Stopping test
155162
stopping_test = test_stopping_criterion(param,stats)
@@ -463,3 +470,4 @@ function add_cuts_to_model!(model::SPModel, t::Int64, problem::JuMP.Model, V::Po
463470
@constraint(problem, V.betas[i] + dot(lambda, xf) <= alpha)
464471
end
465472
end
473+

src/cutpruning.jl

Lines changed: 197 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
1+
#############################################################################
12
# Copyright 2015, Vincent Leclere, Francois Pacaud and Henri Gerard
23
# This Source Code Form is subject to the terms of the Mozilla Public
34
# License, v. 2.0. If a copy of the MPL was not distributed with this
45
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
56
#############################################################################
6-
# Define the Forward / Backward iterations of the SDDP algorithm
7-
#############################################################################
7+
8+
type Territories
9+
ncuts::Int
10+
territories::Array{Array}
11+
nstates::Int
12+
states::Array{Float64, 2}
13+
end
14+
15+
16+
""" Remove redundant cuts in Polyhedral Value function `V`"""
17+
function remove_cuts(V::PolyhedralFunction)
18+
Vf = hcat(V.lambdas, V.betas)
19+
Vf = unique(Vf, 1)
20+
return PolyhedralFunction(Vf[:, end], Vf[:, 1:end-1], size(Vf)[1])
21+
end
22+
23+
24+
""" Remove redundant cuts in a vector of Polyhedral Functions `Vts`."""
25+
function remove_redundant_cuts!(Vts::Vector{PolyhedralFunction})
26+
n_functions = length(Vts)-1
27+
for i in 1:n_functions
28+
Vts[i] = remove_cuts(Vts[i])
29+
end
30+
end
831

932

1033
"""
@@ -15,39 +38,53 @@ Exact pruning of all polyhedral functions in input array.
1538
* `params::SDDPparameters`:
1639
* `Vector{PolyhedralFunction}`:
1740
Polyhedral functions where cuts will be removed
18-
* `iteration_count::Int64`:
41+
* `trajectories::Array{Float64, 3}`
42+
Previous trajectories
43+
* `territory::Array{Territories}`
44+
Container storing the territory for each cuts
45+
* `it::Int64`:
1946
current iteration number
2047
* `verbose::Int64`
2148
"""
2249
function prune_cuts!(model::SPModel,
2350
param::SDDPparameters,
2451
V::Vector{PolyhedralFunction},
25-
iteration_count::Int64,
52+
trajectories::Array{Float64, 3},
53+
territory::Union{Void, Array{Territories}},
54+
it::Int64,
2655
verbose::Int64)
27-
# If specified, prune cuts:
28-
if (param.compute_cuts_pruning > 0) && (iteration_count%param.compute_cuts_pruning==0)
29-
(verbose > 0) && println("Prune cuts ...")
30-
remove_redundant_cuts!(V)
31-
for i in 1:length(V)-1
32-
V[i] = exact_prune_cuts(model, param, V[i])
56+
# Basic pruning: remove redundant cuts
57+
remove_redundant_cuts!(V)
58+
59+
# If pruning is performed with territory heuristic, update territory
60+
# at given iteration:
61+
if param.pruning[:type] == "territory"
62+
for t in 1:model.stageNumber-1
63+
states = reshape(trajectories[t, :, :], param.forwardPassNumber, model.dimStates)
64+
find_territory!(territory[t], V[t], states)
3365
end
34-
problems = hotstart_SDDP(model, param, V)
3566
end
36-
end
3767

38-
""" Remove redundant cuts in Polyhedral Value function `V`"""
39-
function remove_cuts(V::PolyhedralFunction)
40-
Vf = hcat(V.lambdas, V.betas)
41-
Vf = unique(Vf, 1)
42-
return PolyhedralFunction(Vf[:, end], Vf[:, 1:end-1], size(Vf)[1])
43-
end
68+
# If specified to prune cuts at this iteration, do it:
69+
if param.pruning[:pruning] && (it%param.pruning[:period]==0)
70+
# initial number of cuts:
71+
ncuts_initial = get_total_number_cuts(V)
72+
(verbose > 0) && print("Prune cuts ...")
4473

74+
for i in 1:length(V)-1
75+
if param.pruning[:type] == "exact"
76+
# apply exact cuts pruning:
77+
V[i] = exact_prune_cuts(model, param, V[i])
78+
elseif param.pruning[:type] == "territory"
79+
# apply heuristic to prune cuts:
80+
V[i] = remove_empty_cuts!(territory[i], V[i])
81+
end
82+
end
4583

46-
""" Remove redundant cuts in a vector of Polyhedral Functions `Vts`."""
47-
function remove_redundant_cuts!(Vts::Vector{PolyhedralFunction})
48-
n_functions = length(Vts)-1
49-
for i in 1:n_functions
50-
Vts[i] = remove_cuts(Vts[i])
84+
# final number of cuts:
85+
ncuts_final = get_total_number_cuts(V)
86+
87+
(verbose > 0) && println(" Deflation: ", ncuts_final/ncuts_initial)
5188
end
5289
end
5390

@@ -111,3 +148,140 @@ function is_cut_relevant(model::SPModel, k::Int, Vt::PolyhedralFunction, solver;
111148
sol = getobjectivevalue(m)
112149
return sol < epsilon
113150
end
151+
152+
153+
########################################
154+
# Territory algorithm
155+
########################################
156+
157+
Territories(ndim) = Territories(0, [], 0, Array{Float64}(0, ndim))
158+
159+
160+
""" Update territories with cuts previously computed during backward pass. """
161+
function find_territory!(territory, V, states)
162+
nc = V.numCuts
163+
# get number of new positions to analyse:
164+
nx = size(states, 1)
165+
nt = nc - territory.ncuts
166+
167+
for i in 1:nt
168+
add_cut!(territory)
169+
update_territory!(territory, V, nc - nt + i)
170+
end
171+
172+
# ensure that territory has the same number of cuts as V!
173+
assert(territory.ncuts == V.numCuts)
174+
175+
for i in 1:nx
176+
x = collect(states[i, :])
177+
add_state!(territory, V, x)
178+
end
179+
180+
end
181+
182+
183+
"""Update territories considering new cut with index `indcut`."""
184+
function update_territory!(territory, V, indcut)
185+
for k in 1:territory.ncuts
186+
if k == indcut
187+
continue
188+
end
189+
todelete = []
190+
for (num, (ix, cost)) in enumerate(territory.territories[k])
191+
x = collect(territory.states[ix, :])
192+
193+
costnewcut = cutvalue(V, indcut, x)
194+
195+
if costnewcut > cost
196+
push!(todelete, num)
197+
push!(territory.territories[indcut], (ix, costnewcut))
198+
end
199+
end
200+
deleteat!(territory.territories[k], todelete)
201+
end
202+
end
203+
204+
205+
"""Add cut to `territory`."""
206+
function add_cut!(territory)
207+
push!(territory.territories, [])
208+
territory.ncuts += 1
209+
end
210+
211+
212+
"""Add a new state and update territories."""
213+
function add_state!(territory::Territories, V::PolyhedralFunction, x::Array{Float64})
214+
# Get cut which is the supremum at point `x`:
215+
bcost, bcuts = optimalcut(x, V)
216+
217+
# Add `x` to the territory of cut `bcuts`:
218+
territory.nstates += 1
219+
push!(territory.territories[bcuts], (territory.nstates, bcost))
220+
221+
# Add `x` to the list of visited state:
222+
territory.states = vcat(territory.states, x')
223+
end
224+
225+
226+
"""Remove empty cuts in PolyhedralFunction"""
227+
function remove_empty_cuts!(territory::Territories, V::PolyhedralFunction)
228+
assert(territory.ncuts == V.numCuts)
229+
230+
nstates = [length(terr) for terr in territory.territories]
231+
active_cuts = nstates .> 0
232+
233+
territory.territories = territory.territories[active_cuts]
234+
territory.ncuts = sum(active_cuts)
235+
return PolyhedralFunction(V.betas[active_cuts],
236+
V.lambdas[active_cuts, :],
237+
sum(active_cuts))
238+
end
239+
240+
241+
"""Get cut which approximate the best value function at point `x`."""
242+
function optimalcut(xf::Vector{Float64}, V::PolyhedralFunction)
243+
bestcost = -Inf::Float64
244+
bestcut = -1
245+
nstates = size(V.lambdas, 2)
246+
ncuts = size(V.lambdas, 1)
247+
248+
@inbounds for i in 1:ncuts
249+
cost = V.betas[i]
250+
for j in 1:nstates
251+
cost += V.lambdas[i, j]*xf[j]
252+
end
253+
if cost > bestcost
254+
bestcost = cost
255+
bestcut = i
256+
end
257+
end
258+
return bestcost, bestcut
259+
end
260+
261+
262+
"""
263+
Get approximation of value function at given point `x`.
264+
265+
# Arguments
266+
- `V::PolyhedralFunction`
267+
Approximation of the value function as linear cuts
268+
- `indc::Int64`
269+
Index of cut to consider
270+
- `x::Array{Float64}`
271+
Coordinates of state
272+
273+
# Return
274+
`cost::Float64`
275+
Value of cut `indc` at point `x`
276+
"""
277+
function cutvalue(V::PolyhedralFunction, indc::Int, x::Array{Float64})
278+
cost = V.betas[indc]
279+
for j in 1:size(V.lambdas, 2)
280+
cost += V.lambdas[indc, j]*x[j]
281+
end
282+
return cost
283+
end
284+
285+
# Return total number of cuts in PolyhedralFunction array:
286+
get_total_number_cuts(V::Array{PolyhedralFunction}) = sum([v.numCuts for v in V])
287+

src/objects.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type PolyhedralFunction
1919
numCuts::Int64
2020
end
2121

22-
PolyhedralFunction(ndim) = PolyhedralFunction([], Array{Float64}(0, ndim), 0)
22+
PolyhedralFunction(ndim) = PolyhedralFunction([], Array{Float64}(0, ndim), 0)
2323

2424

2525
type LinearSPModel <: SPModel
@@ -152,7 +152,7 @@ type SDDPparameters
152152
# Maximum iterations of the SDDP algorithms:
153153
maxItNumber::Int64
154154
# Prune cuts every %% iterations:
155-
compute_cuts_pruning::Int64
155+
pruning::Dict{Symbol, Any}
156156
# Estimate upper-bound every %% iterations:
157157
compute_ub::Int64
158158
# Number of MonteCarlo simulation to perform to estimate upper-bound:
@@ -165,15 +165,18 @@ type SDDPparameters
165165
acceleration::Dict{Symbol, Float64}
166166

167167
function SDDPparameters(solver; passnumber=10, gap=0.,
168-
max_iterations=20, compute_cuts_pruning=0,
168+
max_iterations=20, prune_cuts=0,
169+
pruning_algo="exact",
169170
compute_ub=-1, montecarlo_final=10000, montecarlo_in_iter = 100,
170171
mipsolver=nothing,
171172
rho0=0., alpha=1.)
172173
is_acc = (rho0 > 0.)
173174
accparams = is_acc? Dict(:ρ0=>rho0, :alpha=>alpha, :rho=>rho0): Dict()
174175

176+
prune_cuts = Dict(:pruning=>prune_cuts>0, :period=>prune_cuts, :type=>pruning_algo)
175177
return new(solver, mipsolver, passnumber, gap,
176-
max_iterations, compute_cuts_pruning, compute_ub, montecarlo_final,montecarlo_in_iter, is_acc, accparams)
178+
max_iterations, prune_cuts, compute_ub,
179+
montecarlo_final, montecarlo_in_iter, is_acc, accparams)
177180
end
178181
end
179182

test/sddp.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ facts("SDDP algorithm: 1D case") do
4646
passnumber=n_scenarios,
4747
gap=epsilon,
4848
max_iterations=max_iterations,
49-
compute_cuts_pruning=1)
49+
prune_cuts=0)
5050

5151
V = nothing
5252
model = StochDynamicProgramming.LinearSPModel(n_stages, u_bounds,
@@ -71,8 +71,8 @@ facts("SDDP algorithm: 1D case") do
7171

7272
# Test if the first subgradient has the same dimension as state:
7373
@fact size(V[1].lambdas, 2) --> model.dimStates
74-
@fact V[1].numCuts --> n_scenarios*max_iterations + n_scenarios
75-
@fact size(V[1].lambdas, 1) --> n_scenarios*max_iterations + n_scenarios
74+
@fact V[1].numCuts < n_scenarios*max_iterations + n_scenarios --> true
75+
@fact size(V[1].lambdas, 1) --> V[1].numCuts
7676

7777
# Test upper bounds estimation with Monte-Carlo:
7878
n_simulations = 100
@@ -109,7 +109,6 @@ facts("SDDP algorithm: 1D case") do
109109
context("Cuts pruning") do
110110
v = V[1]
111111
vt = PolyhedralFunction([v.betas[1]; v.betas[1] - 1.], v.lambdas[[1,1],:], 2)
112-
StochDynamicProgramming.prune_cuts!(model, param, V,1,0)
113112
isactive1 = StochDynamicProgramming.is_cut_relevant(model, 1, vt, param.SOLVER)
114113
isactive2 = StochDynamicProgramming.is_cut_relevant(model, 2, vt, param.SOLVER)
115114
@fact isactive1 --> true
@@ -163,7 +162,7 @@ facts("SDDP algorithm: 1D case") do
163162
context("Stopping criterion") do
164163
# Compute upper bound every %% iterations:
165164
param.compute_ub = 1
166-
param.compute_cuts_pruning = 1
165+
param.pruning = Dict(:pruning=>true, :period=>1, :type=>"exact")
167166
param.maxItNumber = 30
168167
V, pbs = solve_SDDP(model, param, V, 0)
169168
V0 = StochDynamicProgramming.get_lower_bound(model, param, V)

0 commit comments

Comments
 (0)