|
struct InSampleMonteCarlo2 <: SDDP.AbstractSamplingScheme |
|
max_depth::Int |
|
initial_stage::Int |
|
terminate_on_cycle::Bool |
|
terminate_on_dummy_leaf::Bool |
|
rollout_limit::Function |
|
end |
|
|
|
""" |
|
function InSampleMonteCarlo2(; |
|
max_depth::Int = 0, |
|
initial_stage::Int = 1, |
|
terminate_on_cycle::Bool = false, |
|
terminate_on_dummy_leaf::Bool = true, |
|
rollout_limit::Function = i -> typemax(Int), |
|
) |
|
|
|
This function creates an instance of the `InSampleMonteCarlo2` `sampling_scheme`. |
|
This is used for Monte Carlo simulations that may start from an arbitrary stage |
|
(`initial_stage`). |
|
|
|
### Keyword Arguments |
|
|
|
`max_depth` is the maximum number of stages to simulate. |
|
|
|
`initial_stage` is the stage number of the initial stage. |
|
|
|
`terminate_on_cycle` if `true` the simulation will stop if it returns to a previously visited node. |
|
|
|
`terminate_on_dummy_leaf` if `true` the simulation will may stop if it the probabilities of the noises don't sum to 1. |
|
|
|
`rollout_limit` is a function that allows the number of simulated stages to vary with the training iteration number. |
|
""" |
|
function InSampleMonteCarlo2(; |
|
max_depth::Int = 0, |
|
initial_stage::Int = 1, |
|
terminate_on_cycle::Bool = false, |
|
terminate_on_dummy_leaf::Bool = true, |
|
rollout_limit::Function = i -> typemax(Int), |
|
) |
|
if !terminate_on_cycle && !terminate_on_dummy_leaf && max_depth == 0 |
|
error( |
|
"terminate_on_cycle and terminate_on_dummy_leaf cannot both be " * |
|
"false when max_depth=0.", |
|
) |
|
end |
|
new_rollout = let i = 0 |
|
() -> (i += 1; rollout_limit(i)) |
|
end |
|
return InSampleMonteCarlo2( |
|
max_depth, |
|
initial_stage, |
|
terminate_on_cycle, |
|
terminate_on_dummy_leaf, |
|
new_rollout, |
|
) |
|
end |
|
|
|
""" |
|
function SDDP.sample_scenario( |
|
graph::SDDP.PolicyGraph{T}, |
|
sampling_scheme::InSampleMonteCarlo2, |
|
) where {T} |
|
|
|
This function is a version of the SDDP.jl `SDDP.sample_scenario` function which |
|
uses the `InSampleMonteCarlo2` `sampling_scheme`. This enables a simulation to start |
|
from an arbitrary stage (`initial_stage`). |
|
|
|
### Required Arguments |
|
|
|
`graph` is an SDDP.jl `PolicyGraph`. |
|
|
|
`sampling_scheme` is an instance of `InSampleMonteCarlo2` sampling scheme. |
|
""" |
|
function SDDP.sample_scenario( |
|
graph::SDDP.PolicyGraph{T}, |
|
sampling_scheme::InSampleMonteCarlo2, |
|
) where {T} |
|
max_depth = min(sampling_scheme.max_depth, sampling_scheme.rollout_limit()) |
|
# Storage for our scenario. Each tuple is (node_index, noise.term). |
|
scenario_path = Tuple{T,Any}[] |
|
# We only use visited_nodes if terminate_on_cycle=true. Just initialize |
|
# anyway. |
|
visited_nodes = Set{T}() |
|
node_index = sampling_scheme.initial_stage |
|
while true |
|
node = graph[node_index] |
|
noise_terms = node.noise_terms |
|
children = node.children |
|
noise = SDDP.sample_noise(noise_terms) |
|
push!(scenario_path, (node_index, noise)) |
|
# Termination conditions: |
|
if length(children) == 0 |
|
# 1. Our node has no children, i.e., we are at a leaf node. |
|
return scenario_path, false |
|
elseif sampling_scheme.terminate_on_cycle && node_index in visited_nodes |
|
# 2. terminate_on_cycle = true and we have detected a cycle. |
|
return scenario_path, true |
|
elseif 0 < sampling_scheme.max_depth <= length(scenario_path) |
|
# 3. max_depth > 0 and we have explored max_depth number of nodes. |
|
return scenario_path, false |
|
elseif sampling_scheme.terminate_on_dummy_leaf && |
|
rand() < 1 - sum(child.probability for child in children) |
|
# 4. we sample a "dummy" leaf node in the next step due to the |
|
# probability of the child nodes summing to less than one. |
|
return scenario_path, false |
|
end |
|
# We only need to store a list of visited nodes if we want to terminate |
|
# due to the presence of a cycle. |
|
if sampling_scheme.terminate_on_cycle |
|
push!(visited_nodes, node_index) |
|
end |
|
# Sample a new node to transition to. |
|
node_index = SDDP.sample_noise(children)::T |
|
end |
|
# Throw an error because we should never end up here. |
|
return error("Internal SDDP error: something went wrong sampling a scenario.") |
|
end |