Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
odow committed May 13, 2023
1 parent 3ec23cc commit e067774
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 27 deletions.
1 change: 1 addition & 0 deletions docs/src/apireference.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ SDDP.DefaultForwardPass
SDDP.RevisitingForwardPass
SDDP.RiskAdjustedForwardPass
SDDP.AlternativeForwardPass
SDDP.AlternativePostIterationCallback
```

### Risk Measures
Expand Down
2 changes: 1 addition & 1 deletion docs/src/examples/air_conditioning_forward.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ non_convex = create_air_conditioning_model(; convex = false)
SDDP.train(
convex;
forward_pass = SDDP.AlternativeForwardPass(non_convex),
parallel_scheme = SDDP.AlternativeParallelScheme(non_convex),
post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex),
iteration_limit = 10,
)
Test.@test isapprox(SDDP.calculate_bound(non_convex), 62_500.0, atol = 0.1)
Expand Down
45 changes: 39 additions & 6 deletions docs/src/examples/pglib_opf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,25 @@

# # Alternative forward models

# This example demonstrates how to train convex and non-convex models.

# This example uses teh following packages:

using SDDP
import Ipopt
import PowerModels
import Test

function build_model(filename, model_type)
# ## Formulation

# For our model, we build a simple optimal power flow model with a single
# hydro-electric generator.

# The formulation of our optimal power flow problem depends on `model_type`,
# which must be on of the `PowerModels` formulations.

function build_model(model_type)
filename = joinpath(@__DIR__, "pglib_opf_case5_pjm.m")
data = PowerModels.parse_file(filename)
return SDDP.PolicyGraph(
SDDP.UnicyclicGraph(0.95);
Expand Down Expand Up @@ -39,18 +52,38 @@ function build_model(filename, model_type)
end
end

filename = joinpath(@__DIR__, "pglib_opf_case5_pjm.m")
convex = build_model(filename, PowerModels.DCPPowerModel)
# ## Training a convex model

# We can build and train a convex approximation of the optimal power flow
# problem.

# The problem with the convex model is that it does not accurately simulate the
# true dynamics of the problem. Therefore, it under-estimates the true cost of
# operation.

convex = build_model(PowerModels.DCPPowerModel)
SDDP.train(convex; iteration_limit = 10)

non_convex = build_model(filename, PowerModels.ACPPowerModel)
# ## Training a non-convex model

# We can also build and train the true non-convex formulation of the optimal
# power flow problem.

# The problem with the non-convex model is that because it is non-convex,
# SDDP.jl may find a sub-optimal policy. Therefore, it may over-estimate the
# true cost of operation.

non_convex = build_model(PowerModels.ACPPowerModel)
SDDP.train(non_convex; iteration_limit = 10)

convex = build_model(filename, PowerModels.DCPPowerModel)
# ## Combining convex and non-convex models

# As a compromise, we can combine the convex and non-convex models.
convex = build_model(PowerModels.DCPPowerModel)
non_convex = build_model(filename, PowerModels.ACPPowerModel)
SDDP.train(
convex;
forward_pass = SDDP.AlternativeForwardPass(non_convex),
parallel_scheme = SDDP.AlternativeParallelScheme(non_convex),
post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex),
iteration_limit = 10,
)
10 changes: 9 additions & 1 deletion src/algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct Options{T}
duality_handler::AbstractDualityHandler
# A callback called after the forward pass.
forward_pass_callback::Any

post_iteration_callback::Any
# Internal function: users should never construct this themselves.
function Options(
model::PolicyGraph{T},
Expand All @@ -119,6 +119,7 @@ struct Options{T}
forward_pass::AbstractForwardPass = DefaultForwardPass(),
duality_handler::AbstractDualityHandler = ContinuousConicDuality(),
forward_pass_callback = x -> nothing,
post_iteration_callback = result -> nothing
) where {T}
return new{T}(
initial_state,
Expand All @@ -140,6 +141,7 @@ struct Options{T}
forward_pass,
duality_handler,
forward_pass_callback,
post_iteration_callback,
)
end
end
Expand Down Expand Up @@ -913,6 +915,10 @@ Train the policy for `model`.
- `duality_handler::AbstractDualityHandler`: specify a duality handler to use
when creating cuts.
- `post_iteration_callback::Function`: a callback with the signature
`post_iteration_callback(::IterationResult)` that is evaluated after each
iteration of the algorithm.
There is also a special option for infinite horizon problems
- `cycle_discretization_delta`: the maximum distance between states allowed on
Expand Down Expand Up @@ -943,6 +949,7 @@ function train(
add_to_existing_cuts::Bool = false,
duality_handler::AbstractDualityHandler = SDDP.ContinuousConicDuality(),
forward_pass_callback::Function = (x) -> nothing,
post_iteration_callback = result -> nothing,
)
function log_frequency_f(log::Vector{Log})
if mod(length(log), log_frequency) != 0
Expand Down Expand Up @@ -1063,6 +1070,7 @@ function train(
forward_pass,
duality_handler,
forward_pass_callback,
post_iteration_callback,
)
status = :not_solved
try
Expand Down
32 changes: 13 additions & 19 deletions src/alternative_forward.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
A forward pass that simulates using `forward_model`, which may be different to
the model used in the backwards pass.
When using this forward pass, you should almost always pass
[`SDDP.AlternativePostIterationCallback`](@ref) to the `post_iteration_callback`
argument of [`SDDP.train`](@ref).
This forward pass is most useful when the `forward_model` is non-convex and we
use a convex approximation of the model in the backward pass.
Expand Down Expand Up @@ -44,27 +48,17 @@ function forward_pass(
return forward_pass(pass.model, options, pass.forward_pass)
end

struct AlternativeParallelScheme{T} <: AbstractParallelScheme
"""
AlternativePostIterationCallback(forward_model::PolicyGraph)
A post-iteration callback that should be used whenever [`SDDP.AlternativeForwardPass`](@ref)
is used.
"""
struct AlternativePostIterationCallback{T}
model::PolicyGraph{T}
end

Base.show(io::IO, ::AlternativeParallelScheme) = print(io, "alternative")

interrupt(::AlternativeParallelScheme) = nothing

function master_loop(
scheme::AlternativeParallelScheme{T},
model::PolicyGraph{T},
options::Options,
) where {T}
_initialize_solver(model; throw_error = false)
while true
result = iteration(model, options)
slave_update(scheme.model, result)
log_iteration(options)
if result.has_converged
return result.status
end
end
function (callback::AlternativePostIterationCallback)(result::IterationResult)
slave_update(callback.model, result)
return
end
3 changes: 3 additions & 0 deletions src/plugins/parallel_schemes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function master_loop(
_initialize_solver(model; throw_error = false)
while true
result = iteration(model, options)
options.post_iteration_callback(result)
log_iteration(options)
if result.has_converged
return result.status
Expand Down Expand Up @@ -167,6 +168,7 @@ function slave_loop(
results_to_add = IterationResult{T}[]
while true
result = iteration(model, options)
options.post_iteration_callback(result)
# The next four lines are subject to a race condition: if the master closes
# `results` _after_ the call to `isopen` and _before_` the call to `put!` has
# executed, we get an `InvalidStateException`. This gets trapped in the outer
Expand Down Expand Up @@ -243,6 +245,7 @@ function master_loop(
# implementation anyway.
while async.use_master && !isready(results)
result = iteration(model, options)
options.post_iteration_callback(result)
for (_, ch) in updates
put!(ch, result)
end
Expand Down

0 comments on commit e067774

Please sign in to comment.