Skip to content

Commit a2fbfb1

Browse files
committed
[DEV] Integrate territory to SDDP interface
1 parent 5124185 commit a2fbfb1

File tree

4 files changed

+107
-60
lines changed

4 files changed

+107
-60
lines changed

src/SDDPoptimize.jl

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function run_SDDP!(model::SPModel,
6969

7070
#Initialization of the counter
7171
stats = SDDPStat(0, [], [], [], 0)
72-
territory = [Territories(model.dimStates) for i in 1:model.stageNumber-1]
72+
territory = (param.pruning[:type]=="territory")? [Territories(model.dimStates) for i in 1:model.stageNumber-1]: nothing
7373

7474
(verbose > 0) && println("Initialize cuts")
7575

@@ -114,24 +114,29 @@ function run_SDDP!(model::SPModel,
114114
iteration_count += 1
115115
stats.niterations += 1
116116

117-
####################
118-
# If specified, prune cuts:
119-
if (param.pruning[:period] > 0) && (iteration_count%param.pruning[:period]==0)
120-
(verbose > 0) && println("Prune cuts ...")
121-
remove_redundant_cuts!(V)
122-
prune_cuts!(model, param, V)
123-
problems = hotstart_SDDP(model, param, V)
124-
end
125117

126118
# Update estimation of lower bound:
127119
lwb = get_bellman_value(model, param, 1, V[1], model.initialState)
128120
push!(stats.lower_bounds, lwb)
121+
push!(stats.exectime, toq())
122+
push!(stats.upper_bounds, upb)
129123

130-
for t in 1:model.stageNumber-1
131-
states = reshape(stockTrajectories[t, :, :], param.forwardPassNumber, model.dimStates)
132-
find_territory!(territory[t], V[t], states)
124+
if (verbose > 0) && (iteration_count%verbose==0)
125+
print("Pass number ", iteration_count)
126+
(upb < Inf) && print("\tUpper-bound: ", upb)
127+
println("\tLower-bound: ", round(stats.lower_bounds[end], 4),
128+
"\tTime: ", round(stats.exectime[end], 2),"s")
133129
end
134130

131+
####################
132+
# Cut pruning
133+
prune_cuts!(model, param, V, stockTrajectories, territory, iteration_count, verbose)
134+
if (param.pruning[:period] > 0) && (iteration_count%param.pruning[:period]==0)
135+
problems = hotstart_SDDP(model, param, V)
136+
end
137+
138+
139+
135140
####################
136141
# If specified, compute upper-bound:
137142
if (param.compute_upper_bound > 0) && (iteration_count%param.compute_upper_bound==0)
@@ -144,18 +149,8 @@ function run_SDDP!(model::SPModel,
144149
end
145150
end
146151

147-
push!(stats.exectime, toq())
148-
push!(stats.upper_bounds, upb)
149-
150-
if (verbose > 0) && (iteration_count%verbose==0)
151-
print("Pass number ", iteration_count)
152-
(upb < Inf) && print("\tUpper-bound: ", upb)
153-
println("\tLower-bound: ", round(stats.lower_bounds[end], 4),
154-
"\tTime: ", round(stats.exectime[end], 2),"s")
155-
end
156152

157153
end
158-
println([length(v) for v in territory[9].territories])
159154

160155
##########
161156
# Estimate final upper bound with param.monteCarloSize simulations:

src/objects.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ type SDDPparameters
154154
max_iterations=20, prune_cuts=0,
155155
pruning_algo="exact",
156156
compute_ub=-1, montecarlo=10000, mipsolver=nothing)
157-
prune_cuts = Dict(:period=>prune_cuts, :type=>pruning_algo)
157+
prune_cuts = Dict(:pruning=>prune_cuts>0, :period=>prune_cuts, :type=>pruning_algo)
158158
return new(solver, mipsolver, passnumber, gap, max_iterations, prune_cuts, compute_ub, montecarlo)
159159
end
160160
end

src/pruning.jl

Lines changed: 86 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@
55
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
#############################################################################
77

8-
98
type Territories
109
ncuts::Int
1110
territories::Array{Array}
1211
nstates::Int
1312
states::Array{Float64, 2}
1413
end
1514

16-
Territories(ndim) = Territories(0, [], 0, Array{Float64}(0, ndim))
17-
1815

1916
"""
2017
Exact pruning of all polyhedral functions in input array.
@@ -24,10 +21,53 @@ Exact pruning of all polyhedral functions in input array.
2421
* `params::SDDPparameters`:
2522
* `Vector{PolyhedralFunction}`:
2623
Polyhedral functions where cuts will be removed
24+
* `trajectories::Array{Float64, 3}`
25+
Previous trajectories
26+
* `territory::Array{Territories}`
27+
Container storing the territory for each cuts
28+
* `it::Int64`:
29+
current iteration number
30+
* `verbose::Int64`
2731
"""
28-
function prune_cuts!(model::SPModel, params::SDDPparameters, V::Vector{PolyhedralFunction})
29-
for i in 1:length(V)-1
30-
V[i] = exact_prune_cuts(model, params, V[i])
32+
function prune_cuts!(model::SPModel,
33+
param::SDDPparameters,
34+
V::Vector{PolyhedralFunction},
35+
trajectories::Array{Float64, 3},
36+
territory::Union{Void, Array{Territories}},
37+
it::Int64,
38+
verbose::Int64)
39+
# Basic pruning: remove redundant cuts
40+
remove_redundant_cuts!(V)
41+
42+
# If pruning is performed with territory heuristic, update territory
43+
# at given iteration:
44+
if param.pruning[:type] == "territory"
45+
for t in 1:model.stageNumber-1
46+
states = reshape(trajectories[t, :, :], param.forwardPassNumber, model.dimStates)
47+
find_territory!(territory[t], V[t], states)
48+
end
49+
end
50+
51+
# If specified to prune cuts at this iteration, do it:
52+
if param.pruning[:pruning] && (it%param.pruning[:period]==0)
53+
# initial number of cuts:
54+
ncuts_initial = get_total_number_cuts(V)
55+
(verbose > 0) && print("Prune cuts ...")
56+
57+
for i in 1:length(V)-1
58+
if param.pruning[:type] == "exact"
59+
# apply exact cuts pruning:
60+
V[i] = exact_prune_cuts(model, param, V[i])
61+
elseif param.pruning[:type] == "territory"
62+
# apply heuristic to prune cuts:
63+
V[i] = remove_empty_cuts!(territory[i], V[i])
64+
end
65+
end
66+
67+
# final number of cuts:
68+
ncuts_final = get_total_number_cuts(V)
69+
70+
(verbose > 0) && println(" Deflation: ", ncuts_final/ncuts_initial)
3171
end
3272
end
3373

@@ -96,12 +136,8 @@ end
96136
########################################
97137
# Territory algorithm
98138
########################################
99-
function territory_prune_cuts!(territory::Territories,
100-
V::PolyhedralFunction,
101-
states::Array{Float64, 2})
102-
find_territory!(territory, V, states)
103-
return remove_empty_cuts(territory, V)
104-
end
139+
140+
Territories(ndim) = Territories(0, [], 0, Array{Float64}(0, ndim))
105141

106142

107143
""" Update territories with cuts previously computed during backward pass. """
@@ -112,9 +148,11 @@ function find_territory!(territory, V, states)
112148
nt = nc - territory.ncuts
113149

114150
for i in 1:nt
115-
add_cut!(territory, V)
151+
add_cut!(territory)
116152
update_territory!(territory, V, nc - nt + i)
117153
end
154+
155+
# ensure that territory has the same number of cuts as V!
118156
assert(territory.ncuts == V.numCuts)
119157

120158
for i in 1:nx
@@ -125,15 +163,14 @@ function find_territory!(territory, V, states)
125163
end
126164

127165

128-
""" Update territories with new cuts added during previous backward pass. """
166+
"""Update territories considering new cut with index `indcut`."""
129167
function update_territory!(territory, V, indcut)
130168
for k in 1:territory.ncuts
131169
if k == indcut
132170
continue
133171
end
134-
terr = copy(territory.territories[k])
135172
todelete = []
136-
for (num, (ix, cost)) in enumerate(terr)
173+
for (num, (ix, cost)) in enumerate(territory.territories[k])
137174
x = collect(territory.states[ix, :])
138175

139176
costnewcut = cutvalue(V, indcut, x)
@@ -148,31 +185,32 @@ function update_territory!(territory, V, indcut)
148185
end
149186

150187

151-
"""Add a new cut to the"""
152-
function add_cut!(territory, V)
188+
"""Add cut to `territory`."""
189+
function add_cut!(territory)
153190
push!(territory.territories, [])
154191
territory.ncuts += 1
155192
end
156193

157194

158-
function add_state!(territory, V, x)
195+
"""Add a new state and update territories."""
196+
function add_state!(territory::Territories, V::PolyhedralFunction, x::Array{Float64})
197+
# Get cut which is the supremum at point `x`:
159198
bcost, bcuts = optimalcut(x, V)
160199

161-
indx = territory.nstates + 1
162-
push!(territory.territories[bcuts], (indx, bcost))
200+
# Add `x` to the territory of cut `bcuts`:
201+
territory.nstates += 1
202+
push!(territory.territories[bcuts], (territory.nstates, bcost))
163203

204+
# Add `x` to the list of visited state:
164205
territory.states = vcat(territory.states, x')
165-
territory.nstates += 1
166206
end
167207

168208

169-
"""
170-
Remove empty cuts in PolyhedralFunction
171-
"""
172-
function remove_empty_cuts!(territory, V)
173-
assert(length(territory) == V.numCuts)
209+
"""Remove empty cuts in PolyhedralFunction"""
210+
function remove_empty_cuts!(territory::Territories, V::PolyhedralFunction)
211+
assert(territory.ncuts == V.numCuts)
174212

175-
nstates = [length(cont) for cont in territory.territories]
213+
nstates = [length(terr) for terr in territory.territories]
176214
active_cuts = nstates .> 0
177215

178216
territory.territories = territory.territories[active_cuts]
@@ -183,9 +221,7 @@ function remove_empty_cuts!(territory, V)
183221
end
184222

185223

186-
"""
187-
Get cut which approximate the best value function at point `x`.
188-
"""
224+
"""Get cut which approximate the best value function at point `x`."""
189225
function optimalcut(xf::Vector{Float64}, V::PolyhedralFunction)
190226
bestcost = -Inf::Float64
191227
bestcut = -1
@@ -206,12 +242,29 @@ function optimalcut(xf::Vector{Float64}, V::PolyhedralFunction)
206242
end
207243

208244

209-
""" Get approximation of value function at given point `x`. """
210-
function cutvalue(V, indc, x)
245+
"""
246+
Get approximation of value function at given point `x`.
247+
248+
# Arguments
249+
- `V::PolyhedralFunction`
250+
Approximation of the value function as linear cuts
251+
- `indc::Int64`
252+
Index of cut to consider
253+
- `x::Array{Float64}`
254+
Coordinates of state
255+
256+
# Return
257+
`cost::Float64`
258+
Value of cut `indc` at point `x`
259+
"""
260+
function cutvalue(V::PolyhedralFunction, indc::Int, x::Array{Float64})
211261
cost = V.betas[indc]
212262
for j in 1:size(V.lambdas, 2)
213263
cost += V.lambdas[indc, j]*x[j]
214264
end
215265
return cost
216266
end
217267

268+
# Return total number of cuts in PolyhedralFunction array:
269+
get_total_number_cuts(V::Array{PolyhedralFunction}) = sum([v.numCuts for v in V])
270+

test/sddp.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ facts("SDDP algorithm: 1D case") do
7070

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

7676
# Test upper bounds estimation with Monte-Carlo:
7777
n_simulations = 100
@@ -108,7 +108,6 @@ facts("SDDP algorithm: 1D case") do
108108
context("Cuts pruning") do
109109
v = V[1]
110110
vt = PolyhedralFunction([v.betas[1]; v.betas[1] - 1.], v.lambdas[[1,1],:], 2)
111-
StochDynamicProgramming.prune_cuts!(model, params, V)
112111
isactive1 = StochDynamicProgramming.is_cut_relevant(model, 1, vt, params.SOLVER)
113112
isactive2 = StochDynamicProgramming.is_cut_relevant(model, 2, vt, params.SOLVER)
114113
@fact isactive1 --> true
@@ -153,7 +152,7 @@ facts("SDDP algorithm: 1D case") do
153152
context("Stopping criterion") do
154153
# Compute upper bound every %% iterations:
155154
params.compute_upper_bound = 1
156-
params.pruning = Dict(:period=>1, :type=>"exact")
155+
params.pruning = Dict(:pruning=>true, :period=>1, :type=>"exact")
157156
params.maxItNumber = 30
158157
V, pbs = solve_SDDP(model, params, V, 0)
159158
V0 = StochDynamicProgramming.get_lower_bound(model, params, V)

0 commit comments

Comments
 (0)