API Reference
Policy graphs
SDDP.Graph — TypeGraph(root_node::T) where TCreate an empty graph struture with the root node root_node.
SDDP.add_node — Functionadd_node(graph::Graph{T}, node::T) where {T}Add a node to the graph graph.
Examples
add_node(graph, :A)SDDP.add_edge — Functionadd_edge(graph::Graph{T}, edge::Pair{T, T}, probability::Float64) where {T}Add an edge to the graph graph.
Examples
add_edge(graph, 1 => 2, 0.9)
add_edge(graph, :root => :A, 1.0)SDDP.add_ambiguity_set — Functionadd_ambiguity_set(
graph::Graph{T},
set::Vector{T},
lipschitz::Vector{Float64},
) where {T}Add set to the belief partition of graph.
lipschitz is a vector of Lipschitz constants, with one element for each node in set. The Lipschitz constant is the maximum slope of the cost-to-go function with respect to the belief state associated with each node at any point in the state-space.
Examples
graph = LinearGraph(3)
add_ambiguity_set(graph, [1, 2], [1e3, 1e2])
add_ambiguity_set(graph, [3], [1e5])add_ambiguity_set(graph::Graph{T}, set::Vector{T}, lipschitz::Float64)Add set to the belief partition of graph.
lipschitz is a Lipschitz constant for each node in set. The Lipschitz constant is the maximum slope of the cost-to-go function with respect to the belief state associated with each node at any point in the state-space.
Examples
graph = LinearGraph(3)
add_ambiguity_set(graph, [1, 2], 1e3)
add_ambiguity_set(graph, [3], 1e5)SDDP.LinearGraph — FunctionLinearGraph(stages::Int)SDDP.MarkovianGraph — FunctionMarkovianGraph(transition_matrices::Vector{Matrix{Float64}})Construct a Markovian graph from the vector of transition matrices.
transition_matrices[t][i, j] gives the probability of transitioning from Markov state i in stage t - 1 to Markov state j in stage t.
The dimension of the first transition matrix should be (1, N), and transition_matrics[1][1, i] is the probability of transitioning from the root node to the Markov state i.
MarkovianGraph(;
stages::Int,
transition_matrix::Matrix{Float64},
root_node_transition::Vector{Float64},
)Construct a Markovian graph object with stages number of stages and time-independent Markov transition probabilities.
transition_matrix must be a square matrix, and the probability of transitioning from Markov state i in stage t to Markov state j in stage t + 1 is given by transition_matrix[i, j].
root_node_transition[i] is the probability of transitioning from the root node to Markov state i in the first stage.
MarkovianGraph(
simulator::Function;
budget::Union{Int,Vector{Int}},
scenarios::Int = 1000,
)Construct a Markovian graph by fitting Markov chain to scenarios generated by simulator().
budget is the total number of nodes in the resulting Markov chain. This can either be specified as a single Int, in which case we will attempt to intelligently distributed the nodes between stages. Alternatively, budget can be a Vector{Int}, which details the number of Markov state to have in each stage.
SDDP.UnicyclicGraph — FunctionUnicyclicGraph(discount_factor::Float64)Construct a graph composed of a single cycle, with a probability of discount_factor of continuing the cycle.
SDDP.LinearPolicyGraph — FunctionLinearPolicyGraph(builder::Function; stages::Int, kwargs...)Create a linear policy graph with stages number of stages.
See SDDP.PolicyGraph for the other keyword arguments.
SDDP.MarkovianPolicyGraph — FunctionMarkovianPolicyGraph(
builder::Function;
transition_matrices::Vector{Array{Float64,2}},
kwargs...
)Create a Markovian policy graph based on the transition matrices given in transition_matrices.
transition_matrices[t][i, j] gives the probability of transitioning from Markov state i in stage t - 1 to Markov state j in stage t.
The dimension of the first transition matrix should be (1, N), and transition_matrics[1][1, i] is the probability of transitioning from the root node to the Markov state i.
See SDDP.MarkovianGraph for other ways of specifying a Markovian policy graph.
See SDDP.PolicyGraph for the other keyword arguments.
SDDP.PolicyGraph — TypePolicyGraph(
builder::Function,
graph::Graph{T};
sense::Symbol = :Min,
lower_bound = -Inf,
upper_bound = Inf,
optimizer = nothing,
bellman_function = nothing,
direct_mode::Bool = false,
) where {T}Construct a policy graph based on the graph structure of graph. (See SDDP.Graph for details.)
Examples
function builder(subproblem::JuMP.Model, index)
# ... subproblem definition ...
end
model = PolicyGraph(
builder,
graph;
lower_bound = 0.0,
optimizer = HiGHS.Optimizer,
direct_mode = false
)Or, using the Julia do ... end syntax:
model = PolicyGraph(
graph;
lower_bound = 0.0,
optimizer = HiGHS.Optimizer,
direct_mode = true
) do subproblem, index
# ... subproblem definitions ...
endSubproblem definition
SDDP.@stageobjective — Macro@stageobjective(subproblem, expr)Set the stage-objective of subproblem to expr.
Examples
@stageobjective(subproblem, 2x + y)SDDP.parameterize — Functionparameterize(
modify::Function,
subproblem::JuMP.Model,
realizations::Vector{T},
probability::Vector{Float64} = fill(1.0 / length(realizations))
) where {T}Add a parameterization function modify to subproblem. The modify function takes one argument and modifies subproblem based on the realization of the noise sampled from realizations with corresponding probabilities probability.
In order to conduct an out-of-sample simulation, modify should accept arguments that are not in realizations (but still of type T).
Examples
SDDP.parameterize(subproblem, [1, 2, 3], [0.4, 0.3, 0.3]) do ω
JuMP.set_upper_bound(x, ω)
endparameterize(node::Node, noise)Parameterize node node with the noise noise.
SDDP.add_objective_state — Functionadd_objective_state(update::Function, subproblem::JuMP.Model; kwargs...)Add an objective state variable to subproblem.
Required kwargs are:
initial_value: The initial value of the objective state variable at the root node.lipschitz: The lipschitz constant of the objective state variable.
Setting a tight value for the lipschitz constant can significantly improve the speed of convergence.
Optional kwargs are:
lower_bound: A valid lower bound for the objective state variable. Can be-Inf.upper_bound: A valid upper bound for the objective state variable. Can be+Inf.
Setting tight values for these optional variables can significantly improve the speed of convergence.
If the objective state is N-dimensional, each keyword argument must be an NTuple{N,Float64}. For example, initial_value = (0.0, 1.0).
SDDP.objective_state — Functionobjective_state(subproblem::JuMP.Model)Return the current objective state of the problem.
Can only be called from SDDP.parameterize.
SDDP.Noise — TypeNoise(support, probability)An atom of a discrete random variable at the point of support support and associated probability probability.
Training the policy
SDDP.numerical_stability_report — Functionnumerical_stability_report(
[io::IO=stdout,]
model::PolicyGraph,
by_node::Bool = false,
print::Bool = true,
warn::Bool = true,
)Print a report identifying possible numeric stability issues.
- If
by_node, print a report for each node in the graph. - If
print, print toio. - If
warn, warn if the coefficients may cause numerical issues.
SDDP.train — FunctionSDDP.train(model::PolicyGraph; kwargs...)Train the policy for model.
Keyword arguments
iteration_limit::Int: number of iterations to conduct before termination.time_limit::Float64: number of seconds to train before termination.stoping_rules: a vector ofSDDP.AbstractStoppingRules.print_level::Int: control the level of printing to the screen. Defaults to1. Set to0to disable all printing.log_file::String: filepath at which to write a log of the training progress. Defaults toSDDP.log.log_frequency::Int: control the frequency with which the logging is outputted (iterations/log). Defaults to1.run_numerical_stability_report::Bool: generate (and print) a numerical stability report prior to solve. Defaults totrue.refine_at_similar_nodes::Bool: if SDDP can detect that two nodes have the same children, it can cheaply add a cut discovered at one to the other. In almost all cases this should be set totrue.cut_deletion_minimum::Int: the minimum number of cuts to cache before deleting cuts from the subproblem. The impact on performance is solver specific; however, smaller values result in smaller subproblems (and therefore quicker solves), at the expense of more time spent performing cut selection.risk_measure: the risk measure to use at each node. Defaults toExpectation.sampling_scheme: a sampling scheme to use on the forward pass of the algorithm. Defaults toInSampleMonteCarlo.backward_sampling_scheme: a backward pass sampling scheme to use on the backward pass of the algorithm. Defaults toCompleteSampler.cut_type: choose betweenSDDP.SINGLE_CUTandSDDP.MULTI_CUTversions of SDDP.dashboard::Bool: open a visualization of the training over time. Defaults tofalse.parallel_scheme::AbstractParallelScheme: specify a scheme for solving in parallel. Defaults toSerial().forward_pass::AbstractForwardPass: specify a scheme to use for the forward passes.forward_pass_resampling_probability::Union{Nothing,Float64}: set to a value in(0, 1)to enableRiskAdjustedForwardPass. Defaults tonothing(disabled).add_to_existing_cuts::Bool: set totrueto allow training a model that was previously trained. Defaults tofalse.duality_handler::AbstractDualityHandler: specify a duality handler to use when creating cuts.
There is also a special option for infinite horizon problems
cycle_discretization_delta: the maximum distance between states allowed on the forward pass. This is for advanced users only and needs to be used in conjunction with a differentsampling_scheme.
SDDP.termination_status — Functiontermination_status(model::PolicyGraph)Query the reason why the training stopped.
SDDP.write_cuts_to_file — Functionwrite_cuts_to_file(model::PolicyGraph{T}, filename::String) where {T}Write the cuts that form the policy in model to filename in JSON format.
See also SDDP.read_cuts_from_file.
SDDP.read_cuts_from_file — Functionread_cuts_from_file(
model::PolicyGraph{T},
filename::String;
node_name_parser::Function = _node_name_parser,
) where {T}Read cuts (saved using SDDP.write_cuts_to_file) from filename into model.
Since T can be an arbitrary Julia type, the conversion to JSON is lossy. When reading, read_cuts_from_file only supports T=Int, T=NTuple{N, Int}, and T=Symbol. If you have manually created a policy graph with a different node type T, provide a function node_name_parser with the signature node_name_parser(T, name::String)::T where {T} that returns the name of each node given the string name name.
See also SDDP.write_cuts_to_file.
SDDP.write_log_to_csv — Functionwrite_log_to_csv(model::PolicyGraph, filename::String)Write the log of the most recent training to a csv for post-analysis.
Assumes that the model has been trained via SDDP.train.
Stopping rules
SDDP.AbstractStoppingRule — TypeAbstractStoppingRuleThe abstract type for the stopping-rule interface.
You need to define the following methods:
SDDP.stopping_rule_status — Functionstopping_rule_status(::AbstractStoppingRule)::SymbolReturn a symbol describing the stopping rule.
SDDP.convergence_test — Functionconvergence_test(
model::PolicyGraph,
log::Vector{Log},
::AbstractStoppingRule,
)::BoolReturn a Bool indicating if the algorithm should terminate the training.
Sampling schemes
SDDP.AbstractSamplingScheme — TypeAbstractSamplingSchemeThe abstract type for the sampling-scheme interface.
You need to define the following methods:
SDDP.sample_scenario — Functionsample_scenario(graph::PolicyGraph{T}, ::AbstractSamplingScheme) where {T}Sample a scenario from the policy graph graph based on the sampling scheme.
Returns ::Tuple{Vector{Tuple{T, <:Any}}, Bool}, where the first element is the scenario, and the second element is a Boolean flag indicating if the scenario was terminated due to the detection of a cycle.
The scenario is a list of tuples (type Vector{Tuple{T, <:Any}}) where the first component of each tuple is the index of the node, and the second component is the stagewise-independent noise term observed in that node.
SDDP.InSampleMonteCarlo — TypeInSampleMonteCarlo(;
max_depth::Int = 0,
terminate_on_cycle::Function = false,
terminate_on_dummy_leaf::Function = true,
rollout_limit::Function = (i::Int) -> typemax(Int),
initial_node::Any = nothing,
)A Monte Carlo sampling scheme using the in-sample data from the policy graph definition.
If terminate_on_cycle, terminate the forward pass once a cycle is detected. If max_depth > 0, return once max_depth nodes have been sampled. If terminate_on_dummy_leaf, terminate the forward pass with 1 - probability of sampling a child node.
Note that if terminate_on_cycle = false and terminate_on_dummy_leaf = false then max_depth must be set > 0.
Control which node the trajectories start from using initial_node. If it is left as nothing, the root node is used as the starting node.
You can use rollout_limit to set iteration specific depth limits. For example:
InSampleMonteCarlo(rollout_limit = i -> 2 * i)SDDP.OutOfSampleMonteCarlo — TypeOutOfSampleMonteCarlo(
f::Function,
graph::PolicyGraph;
use_insample_transition::Bool = false,
max_depth::Int = 0,
terminate_on_cycle::Bool = false,
terminate_on_dummy_leaf::Bool = true,
rollout_limit::Function = i -> typemax(Int),
initial_node = nothing,
)Create a Monte Carlo sampler using out-of-sample probabilities and/or supports for the stagewise-independent noise terms, and out-of-sample probabilities for the node-transition matrix.
f is a function that takes the name of a node and returns a tuple containing a vector of new SDDP.Noise terms for the children of that node, and a vector of new SDDP.Noise terms for the stagewise-independent noise.
If f is called with the name of the root node (e.g., 0 in a linear policy graph, (0, 1) in a Markovian Policy Graph), then return a vector of SDDP.Noise for the children of the root node.
If use_insample_transition, the in-sample transition probabilities will be used. Therefore, f should only return a vector of the stagewise-independent noise terms, and f will not be called for the root node.
If terminate_on_cycle, terminate the forward pass once a cycle is detected. If max_depth > 0, return once max_depth nodes have been sampled. If terminate_on_dummy_leaf, terminate the forward pass with 1 - probability of sampling a child node.
Note that if terminate_on_cycle = false and terminate_on_dummy_leaf = false then max_depth must be set > 0.
Control which node the trajectories start from using initial_node. If it is left as nothing, the root node is used as the starting node.
You can use rollout_limit to set iteration specific depth limits. For example:
OutOfSampleMonteCarlo(rollout_limit = i -> 2 * i)Examples
Given linear policy graph graph with T stages:
sampler = OutOfSampleMonteCarlo(graph) do node
if node == 0
return [SDDP.Noise(1, 1.0)]
else
noise_terms = [SDDP.Noise(node, 0.3), SDDP.Noise(node + 1, 0.7)]
children = node < T ? [SDDP.Noise(node + 1, 0.9)] : SDDP.Noise{Int}[]
return children, noise_terms
end
endGiven linear policy graph graph with T stages:
sampler = OutOfSampleMonteCarlo(graph, use_insample_transition=true) do node
return [SDDP.Noise(node, 0.3), SDDP.Noise(node + 1, 0.7)]
endSDDP.Historical — TypeHistorical(
scenarios::Vector{Vector{Tuple{T,S}}},
probability::Vector{Float64};
terminate_on_cycle::Bool = false,
) where {T,S}A sampling scheme that samples a scenario from the vector of scenarios scenarios according to probability.
Examples
Historical(
[
[(1, 0.5), (2, 1.0), (3, 0.5)],
[(1, 0.5), (2, 0.0), (3, 1.0)],
[(1, 1.0), (2, 0.0), (3, 0.0)]
],
[0.2, 0.5, 0.3],
)Historical(
scenarios::Vector{Vector{Tuple{T,S}}};
terminate_on_cycle::Bool = false,
) where {T,S}A deterministic sampling scheme that iterates through the vector of provided scenarios.
Examples
Historical([
[(1, 0.5), (2, 1.0), (3, 0.5)],
[(1, 0.5), (2, 0.0), (3, 1.0)],
[(1, 1.0), (2, 0.0), (3, 0.0)],
])Historical(
scenario::Vector{Tuple{T,S}};
terminate_on_cycle::Bool = false,
) where {T,S}A deterministic sampling scheme that always samples scenario.
Examples
Historical([(1, 0.5), (2, 1.5), (3, 0.75)])SDDP.PSRSamplingScheme — TypePSRSamplingScheme(N::Int; sampling_scheme = InSampleMonteCarlo())A sampling scheme with N scenarios, similar to how PSR does it.
Parallel schemes
SDDP.AbstractParallelScheme — TypeAbstractParallelSchemeAbstract type for different parallelism schemes.
SDDP.Serial — TypeSerial()Run SDDP in serial mode.
SDDP.Asynchronous — TypeAsynchronous(init_callback::Function, slave_pids::Vector{Int} = workers())Run SDDP in asynchronous mode workers with pid's slave_pids.
After initializing the models on each worker, call init_callback(model). Note that init_callback is run locally on the worker and not on the master thread.
Asynchronous(slave_pids::Vector{Int} = workers())Run SDDP in asynchronous mode workers with pid's slave_pids.
Forward passes
SDDP.AbstractForwardPass — TypeAbstractForwardPassAbstract type for different forward passes.
SDDP.DefaultForwardPass — TypeDefaultForwardPass(; include_last_node::Bool = true)The default forward pass.
If include_last_node = false and the sample terminated due to a cycle, then the last node (which forms the cycle) is omitted. This can be useful option to set when training, but it comes at the cost of not knowing which node formed the cycle (if there are multiple possibilities).
SDDP.RevisitingForwardPass — TypeRevisitingForwardPass(
period::Int = 500;
sub_pass::AbstractForwardPass = DefaultForwardPass(),
)A forward pass scheme that generate period new forward passes (using sub_pass), then revisits all previously explored forward passes. This can be useful to encourage convergence at a diversity of points in the state-space.
Set period = typemax(Int) to disable.
For example, if period = 2, then the forward passes will be revisited as follows: 1, 2, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 1, 2, ....
SDDP.RiskAdjustedForwardPass — TypeRiskAdjustedForwardPass(;
forward_pass::AbstractForwardPass,
risk_measure::AbstractRiskMeasure,
resampling_probability::Float64,
rejection_count::Int = 5,
)A forward pass that resamples a previous forward pass with resampling_probability probability, and otherwise samples a new forward pass using forward_pass.
The forward pass to revisit is chosen based on the risk-adjusted (using risk_measure) probability of the cumulative stage objectives.
Note that this objective corresponds to the first time we visited the trajectory. Subsequent visits may have improved things, but we don't have the mechanisms in-place to update it. Therefore, remove the forward pass from resampling consideration after rejection_count revisits.
Risk Measures
SDDP.AbstractRiskMeasure — TypeAbstractRiskMeasureThe abstract type for the risk measure interface.
You need to define the following methods:
SDDP.adjust_probability — Functionadjust_probability(
measure::Expectation
risk_adjusted_probability::Vector{Float64},
original_probability::Vector{Float64},
noise_support::Vector{Noise{T}},
objective_realizations::Vector{Float64},
is_minimization::Bool,
) where {T}Duality handlers
SDDP.AbstractDualityHandler — TypeAbstractDualityHandlerThe abstract type for the duality handler interface.
SDDP.ContinuousConicDuality — TypeContinuousConicDuality()Compute dual variables in the backward pass using conic duality, relaxing any binary or integer restrictions as necessary.
Theory
Given the problem
min Cᵢ(x̄, u, w) + θᵢ
st (x̄, x′, u) in Xᵢ(w) ∩ S
x̄ - x == 0 [λ]where S ⊆ ℝ×ℤ, we relax integrality and using conic duality to solve for λ in the problem:
min Cᵢ(x̄, u, w) + θᵢ
st (x̄, x′, u) in Xᵢ(w)
x̄ - x == 0 [λ]SDDP.LagrangianDuality — TypeLagrangianDuality(;
method::LocalImprovementSearch.AbstractSearchMethod =
LocalImprovementSearch.BFGS(100),
)Obtain dual variables in the backward pass using Lagrangian duality.
Arguments
method: theLocalImprovementSearchmethod for maximizing the Lagrangian dual problem.
Theory
Given the problem
min Cᵢ(x̄, u, w) + θᵢ
st (x̄, x′, u) in Xᵢ(w) ∩ S
x̄ - x == 0 [λ]where S ⊆ ℝ×ℤ, we solve the problem max L(λ), where:
L(λ) = min Cᵢ(x̄, u, w) + θᵢ - λ' h(x̄)
st (x̄, x′, u) in Xᵢ(w) ∩ Sand where h(x̄) = x̄ - x.
SDDP.StrengthenedConicDuality — TypeStrengthenedConicDuality()Obtain dual variables in the backward pass using strengthened conic duality.
Theory
Given the problem
min Cᵢ(x̄, u, w) + θᵢ
st (x̄, x′, u) in Xᵢ(w) ∩ S
x̄ - x == 0 [λ]we first obtain an estimate for λ using ContinuousConicDuality.
Then, we evaluate the Lagrangian function:
L(λ) = min Cᵢ(x̄, u, w) + θᵢ - λ' (x̄ - x`)
st (x̄, x′, u) in Xᵢ(w) ∩ Sto obtain a better estimate of the intercept.
SDDP.BanditDuality — TypeBanditDuality()Formulates the problem of choosing a duality handler as a multi-armed bandit problem. The arms to choose between are:
Our problem isn't a typical multi-armed bandit for a two reasons:
- The reward distribution is non-stationary (each arm converges to 0 as it keeps getting pulled.
- The distribution of rewards is dependent on the history of the arms that were chosen.
We choose a very simple heuristic: pick the arm with the best mean + 1 standard deviation. That should ensure we consistently pick the arm with the best likelihood of improving the value function.
In future, we should consider discounting the rewards of earlier iterations, and focus more on the more-recent rewards.
Simulating the policy
SDDP.simulate — Functionsimulate(
model::PolicyGraph,
number_replications::Int = 1,
variables::Vector{Symbol} = Symbol[];
sampling_scheme::AbstractSamplingScheme =
InSampleMonteCarlo(),
custom_recorders = Dict{Symbol, Function}(),
duality_handler::Union{Nothing,AbstractDualityHandler} = nothing,
skip_undefined_variables::Bool = false,
parallel_scheme::AbstractParallelScheme = Serial(),
incoming_state::Dict{String,Float64} = _intial_state(model),
)::Vector{Vector{Dict{Symbol,Any}}}Perform a simulation of the policy model with number_replications replications using the sampling scheme sampling_scheme.
Use incoming_state to pass an initial value of the state variable, if it differs from that at the root node. Each key should be the string name of the state variable.
Returns a vector with one element for each replication. Each element is a vector with one-element for each node in the scenario that was sampled. Each element in that vector is a dictionary containing information about the subproblem that was solved.
In that dictionary there are four special keys:
- :node_index, which records the index of the sampled node in the policy model
- :noise_term, which records the noise observed at the node
- :stage_objective, which records the stage-objective of the subproblem
- :bellman_term, which records the cost/value-to-go of the node.
The sum of :stageobjective + :bellmanterm will equal the objective value of the solved subproblem.
In addition to the special keys, the dictionary will contain the result of JuMP.value(subproblem[key]) for each key in variables. This is useful to obtain the primal value of the state and control variables.
For more complicated data, the custom_recorders keyword argument can be used.
data = Dict{Symbol, Any}()
for (key, recorder) in custom_recorders
data[key] = foo(subproblem)
endFor example, to record the dual of a constraint named my_constraint, pass the following:
simulation_results = SDDP.simulate(model, 2;
custom_recorders = Dict{Symbol, Function}(
:constraint_dual => (sp) -> JuMP.dual(sp[:my_constraint])
)
)The value of the dual in the first stage of the second replication can be accessed as:
simulation_results[2][1][:constraint_dual]If you do not require dual variables (or if they are not available), pass duality_handler = nothing.
If you attempt to simulate the value of a variable that is only defined in some of the stage problems, an error will be thrown. To over-ride this (and return a NaN instead), pass skip_undefined_variables = true.
Use parallel_scheme::[AbstractParallelScheme](@ref) to specify a scheme for simulating in parallel. Defaults to Serial.
SDDP.calculate_bound — FunctionSDDP.calculate_bound(
model::PolicyGraph,
state::Dict{Symbol,Float64},
risk_measure = Expectation(),
)Calculate the lower bound (if minimizing, otherwise upper bound) of the problem model at the point state, assuming the risk measure at the root node is risk_measure.
SDDP.add_all_cuts — Functionadd_all_cuts(model::PolicyGraph)Add all cuts that may have been deleted back into the model.
Explanation
During the solve, SDDP.jl may decide to remove cuts for a variety of reasons.
These can include cuts that define the optimal value function, particularly around the extremes of the state-space (e.g., reservoirs empty).
This function ensures that all cuts discovered are added back into the model.
Decision rules
SDDP.DecisionRule — TypeDecisionRule(model::PolicyGraph{T}; node::T)Create a decision rule for node node in model.
SDDP.evaluate — Functionevaluate(
rule::DecisionRule;
incoming_state::Dict{Symbol,Float64},
noise = nothing,
controls_to_record = Symbol[],
)Evalute the decision rule rule at the point described by the incoming_state and noise.
If the node is deterministic, omit the noise argument.
Pass a list of symbols to controls_to_record to save the optimal primal solution corresponding to the names registered in the model.
evaluate(
V::ValueFunction,
point::Dict{Union{Symbol,String},<:Real}
objective_state = nothing,
belief_state = nothing
)Evaluate the value function V at point in the state-space.
Returns a tuple containing the height of the function, and the subgradient w.r.t. the convex state-variables.
Examples
evaluate(V, Dict(:volume => 1.0))If the state variable is constructed like @variable(sp, volume[1:4] >= 0, SDDP.State, initial_value = 0.0), use [i] to index the state variable:
evaluate(V, Dict(Symbol("volume[1]") => 1.0))You can also use strings or symbols for the keys.
evaluate(V, Dict("volume[1]" => 1))evalute(V::ValueFunction{Nothing, Nothing}; kwargs...)Evalute the value function V at the point in the state-space specified by kwargs.
Examples
evaluate(V; volume = 1)evaluate(
model::PolicyGraph{T},
test_scenarios::TestScenarios{T,S},
) where {T,S}Evaluate the performance of the policy contained in model after a call to train on the scenarios specified by test_scenarios.
Examples
model, test_scenarios = read_from_file("my_model.sof.json")
train(model; iteration_limit = 100)
simulations = evaluate(model, test_scenarios)Visualizing the policy
SDDP.SpaghettiPlot — TypeSDDP.SpaghettiPlot(; stages, scenarios)Initialize a new SpaghettiPlot with stages stages and scenarios number of replications.
SDDP.add_spaghetti — FunctionSDDP.add_spaghetti(data_function::Function, plt::SpaghettiPlot; kwargs...)Description
Add a new figure to the SpaghettiPlot plt, where the y-value of the scenarioth line when x = stage is given by data_function(plt.simulations[scenario][stage]).
Keyword arguments
xlabel: set the xaxis labelylabel: set the yaxis labeltitle: set the title of the plotymin: set the minimum y valueymax: set the maximum y valuecumulative: plot the additive accumulation of the value across the stagesinterpolate: interpolation method for lines between stages.
Defaults to "linear" see the d3 docs for all options.
Examples
simulations = simulate(model, 10)
plt = SDDP.spaghetti_plot(simulations)
SDDP.add_spaghetti(plt; title = "Stage objective") do data
return data[:stage_objective]
endSDDP.publication_plot — FunctionSDDP.publication_plot(
data_function, simulations;
quantile = [0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0],
kwargs...)Create a Plots.jl recipe plot of the simulations.
See Plots.jl for the list of keyword arguments.
Examples
SDDP.publication_plot(simulations; title = "My title") do data
return data[:stage_objective]
endSDDP.ValueFunction — TypeValueFunctionA representation of the value function. SDDP.jl uses the following unique representation of the value function that is undocumented in the literature.
It supports three types of state variables:
- x - convex "resource" states
- b - concave "belief" states
- y - concave "objective" states
In addition, we have three types of cuts:
- Single-cuts (also called "average" cuts in the literature), which involve the risk-adjusted expectation of the cost-to-go.
- Multi-cuts, which use a different cost-to-go term for each realization w.
- Risk-cuts, which correspond to the facets of the dual interpretation of a coherent risk measure.
Therefore, ValueFunction returns a JuMP model of the following form:
V(x, b, y) = min: μᵀb + νᵀy + θ
s.t. # "Single" / "Average" cuts
μᵀb(j) + νᵀy(j) + θ >= α(j) + xᵀβ(j), ∀ j ∈ J
# "Multi" cuts
μᵀb(k) + νᵀy(k) + φ(w) >= α(k, w) + xᵀβ(k, w), ∀w ∈ Ω, k ∈ K
# "Risk-set" cuts
θ ≥ Σ{p(k, w) * φ(w)}_w - μᵀb(k) - νᵀy(k), ∀ k ∈ KSDDP.evaluate — Methodevaluate(
V::ValueFunction,
point::Dict{Union{Symbol,String},<:Real}
objective_state = nothing,
belief_state = nothing
)Evaluate the value function V at point in the state-space.
Returns a tuple containing the height of the function, and the subgradient w.r.t. the convex state-variables.
Examples
evaluate(V, Dict(:volume => 1.0))If the state variable is constructed like @variable(sp, volume[1:4] >= 0, SDDP.State, initial_value = 0.0), use [i] to index the state variable:
evaluate(V, Dict(Symbol("volume[1]") => 1.0))You can also use strings or symbols for the keys.
evaluate(V, Dict("volume[1]" => 1))SDDP.plot — Functionplot(plt::SpaghettiPlot[, filename::String]; open::Bool = true)The SpaghettiPlot plot plt to filename. If filename is not given, it will be saved to a temporary directory. If open = true, then a browser window will be opened to display the resulting HTML file.
Debugging the model
SDDP.write_subproblem_to_file — Functionwrite_subproblem_to_file(
node::Node,
filename::String;
throw_error::Bool = false,
)Write the subproblem contained in node to the file filename.
SDDP.deterministic_equivalent — Functiondeterministic_equivalent(
pg::PolicyGraph{T},
optimizer = nothing;
time_limit::Union{Real,Nothing} = 60.0,
)Form a JuMP model that represents the deterministic equivalent of the problem.
Examples
deterministic_equivalent(model)deterministic_equivalent(model, HiGHS.Optimizer)StochOptFormat
SDDP.write_to_file — Functionwrite_to_file(
model::PolicyGraph,
filename::String;
compression::MOI.FileFormats.AbstractCompressionScheme =
MOI.FileFormats.AutomaticCompression(),
kwargs...
)Write model to filename in the StochOptFormat file format.
Pass an argument to compression to override the default of automatically detecting the file compression to use based on the extension of filename.
See Base.write(::IO, ::PolicyGraph) for information on the keyword arguments that can be provided.
This function is experimental. See the full warning in Base.write(::IO, ::PolicyGraph).
Examples
write_to_file(model, "my_model.sof.json"; test_scenarios = 10)SDDP.read_from_file — Functionread_from_file(
filename::String;
compression::MOI.FileFormats.AbstractCompressionScheme =
MOI.FileFormats.AutomaticCompression(),
kwargs...
)::Tuple{PolicyGraph, TestScenarios}Return a tuple containing a PolicyGraph object and a TestScenarios read from filename in the StochOptFormat file format.
Pass an argument to compression to override the default of automatically detecting the file compression to use based on the extension of filename.
See Base.read(::IO, ::Type{PolicyGraph}) for information on the keyword arguments that can be provided.
This function is experimental. See the full warning in Base.read(::IO, ::Type{PolicyGraph}).
Examples
model, test_scenarios = read_from_file("my_model.sof.json")Base.write — MethodBase.write(
io::IO,
model::PolicyGraph;
test_scenarios::Union{Int, TestScenarios} = 1_000,
kwargs...
)Write model to io in the StochOptFormat file format.
Pass an Int to test_scenarios (default 1_000) to specify the number of test scenarios to generate using the InSampleMonteCarlo sampling scheme. Alternatively, pass a TestScenarios object to manually specify the test scenarios to use.
Any additional kwargs passed to write will be stored in the top-level of the resulting StochOptFormat file. Valid arguments include name, author, date, and description.
WARNING: THIS FUNCTION IS EXPERIMENTAL. THINGS MAY CHANGE BETWEEN COMMITS. YOU SHOULD NOT RELY ON THIS FUNCTIONALITY AS A LONG-TERM FILE FORMAT (YET).
In addition to potential changes to the underlying format, only a subset of possible modifications are supported. These include:
JuMP.fixJuMP.set_lower_boundJuMP.set_upper_boundJuMP.set_normalized_rhs- Changes to the constant or affine terms in a stage objective
If your model uses something other than this, this function will silently write an incorrect formulation of the problem.
Examples
open("my_model.sof.json", "w") do io
write(
io,
model;
test_scenarios = 10,
name = "MyModel",
author = "@odow",
date = "2020-07-20",
description = "Example problem for the SDDP.jl documentation",
)
endBase.read — MethodBase.read(
io::IO,
::Type{PolicyGraph};
bound::Float64 = 1e6,
)::Tuple{PolicyGraph,TestScenarios}Return a tuple containing a PolicyGraph object and a TestScenarios read from io in the StochOptFormat file format.
See also: evaluate.
This function is experimental. Things may change between commits. You should not rely on this functionality as a long-term file format (yet).
In addition to potential changes to the underlying format, only a subset of possible modifications are supported. These include:
- Additive random variables in the constraints or in the objective
- Multiplicative random variables in the objective
If your model uses something other than this, this function may throw an error or silently build a non-convex model.
Examples
open("my_model.sof.json", "r") do io
model, test_scenarios = read(io, PolicyGraph)
endSDDP.evaluate — Methodevaluate(
model::PolicyGraph{T},
test_scenarios::TestScenarios{T,S},
) where {T,S}Evaluate the performance of the policy contained in model after a call to train on the scenarios specified by test_scenarios.
Examples
model, test_scenarios = read_from_file("my_model.sof.json")
train(model; iteration_limit = 100)
simulations = evaluate(model, test_scenarios)SDDP.TestScenarios — TypeTestScenarios{T,S}(scenarios::Vector{TestScenario{T,S}})An AbstractSamplingScheme based on a vector of scenarios.
Each scenario is a vector of Tuple{T, S} where the first element is the node to visit and the second element is the realization of the stagewise-independent noise term. Pass nothing if the node is deterministic.
SDDP.TestScenario — TypeTestScenario{T,S}(probability::Float64, scenario::Vector{Tuple{T,S}})A single scenario for testing.
See also: TestScenarios.