Skip to content

Commit

Permalink
Improve test coverage (#810)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Nov 28, 2024
1 parent ee11833 commit 0c99d6e
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 13 deletions.
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ JSON = "0.21"
JSONSchema = "1"
JuMP = "1"
MutableArithmetics = "1"
Plots = "1"
RecipesBase = "1"
Reexport = "1"
TimerOutputs = "0.5"
Expand All @@ -35,7 +36,8 @@ Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Downloads", "HiGHS", "JSONSchema", "Pkg", "Test"]
test = ["Downloads", "HiGHS", "JSONSchema", "Pkg", "Plots", "Test"]
7 changes: 1 addition & 6 deletions src/plugins/risk_measures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -567,12 +567,7 @@ function adjust_probability(
sum(objective_realizations[i] * p[i] for i in 1:N)
)
JuMP.optimize!(wasserstein)
if JuMP.primal_status(wasserstein) != MOI.FEASIBLE_POINT
error(
"Unable to solver Wasserstein subproblem. Status: ",
JuMP.termination_status(wassserstein),
)
end
@assert JuMP.primal_status(wasserstein) == MOI.FEASIBLE_POINT
copyto!(risk_adjusted_probability, JuMP.value.(p))
return 0.0
end
Expand Down
2 changes: 1 addition & 1 deletion src/visualization/value_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ function get_axis(x::Vector{NTuple{N,T}}) where {N,T}
[xi[changing_index] for xi in x]
end

get_axis(x::Vector{Nothing}) = nothing
get_axis(::Vector{Nothing}) = nothing

function get_axis(X::Vector{Point{Y,B}}) where {Y,B}
for f in [x -> x.x, x -> x.y, x -> x.b]
Expand Down
15 changes: 14 additions & 1 deletion test/binary_expansion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,20 @@ function test_Binary_Expansion()
@test binexpand(0.5, 0.5, 0.01) == binexpand(50, 50)
@test binexpand(0.54, 0.54, 0.01) == binexpand(54, 54)
@test binexpand(0.56, 0.56, 0.01) == binexpand(56, 56)

@test_throws(
ErrorException(
"Cannot perform binary expansion on a negative number." *
"Initial values of state variables must be nonnegative.",
),
binexpand(-1, 5),
)
@test_throws(
ErrorException(
"Cannot perform binary expansion on zero-length " *
"vector. Upper bounds of state variables must be positive.",
),
binexpand(5, 0),
)
@test 0 == bincontract([0])
@test 1 == bincontract([1])
@test 0 == bincontract([0, 0])
Expand Down
15 changes: 15 additions & 0 deletions test/deterministic_equivalent.jl
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,21 @@ function test_vector_valued_functions()
return
end

function test_copy_and_replace_error()
model = SDDP.LinearPolicyGraph(; stages = 2, lower_bound = 0.0) do sp, t
@variable(sp, x >= 0, SDDP.State, initial_value = 0.0)
@constraint(sp, sin(x.out) == x.in)
@stageobjective(sp, x.out)
end
@test_throws(
ErrorException(
"Unable to formulate deterministic equivalent: `copy_and_replace_variables` is not implemented for functions like `sin(x_out) - x_in`.",
),
SDDP.deterministic_equivalent(model),
)
return
end

end # module

TestDeterministicEquivalent.runtests()
11 changes: 11 additions & 0 deletions test/plugins/duality_handlers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,17 @@ function test_BanditDuality_eval()
return
end

function test_deprecate_integrality_handler()
err = try
SDDP._deprecate_integrality_handler()
catch err
err
end
@test_throws err SDDP.SDDiP()
@test_throws err SDDP.ContinuousRelaxation()
return
end

end

TestDualityHandlers.runtests()
34 changes: 34 additions & 0 deletions test/plugins/risk_measures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,29 @@ function test_ModifiedChiSquared_Min_sqrt08()
return
end

function test_ModifiedChiSquared_default()
P, Q = [0.4, 0.6], zeros(2)
F = SDDP.ModifiedChiSquared(0.1)
a = SDDP.adjust_probability(F, Q, P, [:a, :b], [1.0, 1.0], true)
@test iszero(a)
@test P == Q
return
end

function test_ModifiedChiSquared_zero_prob()
P, Q = [1.0, 0.0], zeros(2)
F = SDDP.ModifiedChiSquared(0.1)
a = SDDP.adjust_probability(F, Q, P, [:a, :b], [1.0, 2.0], true)
@test iszero(a)
@test P == Q
P, Q = [0.5, 0.0, 0.5], zeros(3)
F = SDDP.ModifiedChiSquared(0.1)
a = SDDP.adjust_probability(F, Q, P, [:a, :b, :c], [1.0, 1.0, 2.0], true)
@test iszero(a)
@test isapprox(Q, [0.4292893218813453, 0.0, 0.5707106781186547])
return
end

function _default_wasserstein(alpha)
return SDDP.Wasserstein(HiGHS.Optimizer; alpha = alpha) do x, y
return abs(x - y)
Expand Down Expand Up @@ -489,6 +512,17 @@ function test_Entropic()
return
end

function test_Entropic_zero()
# Test that increasing values of θ lead to larger values for F[X].
X = [1.0, 2.0, 3.0]
P = [0.5, 0.5, 0.0]
Q = [NaN, NaN, NaN]
α = SDDP.adjust_probability(SDDP.Entropic(0.0), Q, P, [], X, true)
@test iszero(α)
@test Q == P
return
end

function test_Entropic_Min()
# Test that increasing values of θ lead to larger values for F[X].
X = [1.0, 2.0, 3.0]
Expand Down
26 changes: 22 additions & 4 deletions test/plugins/sampling_schemes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ end

function test_Historical()
@test_throws Exception SDDP.Historical([[1, 2], [3, 4]], [0.6, 0.6])
@test_throws(
ErrorException(
"Probability of historical scenarios must sum to 1. Currently: 1.1.",
),
SDDP.Historical([[(1, 1), (2, 2)]], [1.1]),
)
return
end

Expand All @@ -174,10 +180,11 @@ function test_Historical_SingleTrajectory()
return JuMP.set_upper_bound(x, ω)
end
end
scenario, terminated_due_to_cycle = SDDP.sample_scenario(
model,
SDDP.Historical([(1, 0.1), (2, 0.2), (1, 0.3)]),
)
sampling_scheme = SDDP.Historical([(1, 0.1), (2, 0.2), (1, 0.3)])
@test sprint(show, sampling_scheme) ==
"A Historical sampler with 1 scenarios sampled sequentially."
scenario, terminated_due_to_cycle =
SDDP.sample_scenario(model, sampling_scheme)
@test length(scenario) == 3
@test !terminated_due_to_cycle
@test scenario == [(1, 0.1), (2, 0.2), (1, 0.3)]
Expand Down Expand Up @@ -249,6 +256,7 @@ function test_PSR()
end
end
scheme = SDDP.PSRSamplingScheme(2)
@test sprint(show, scheme) == "A sampler with 0 scenarios like PSR does."
scenario_1, term_1 = SDDP.sample_scenario(model, scheme)
@test length(scenario_1) == 2
@test !term_1
Expand Down Expand Up @@ -345,6 +353,7 @@ function test_SimulatorSamplingScheme()
end
end
sampler = SDDP.SimulatorSamplingScheme(simulator)
@test sprint(show, sampler) == "SimulatorSamplingScheme"
scenario, _ = SDDP.sample_scenario(model, sampler)
@test length(scenario) == 3
@test haskey(graph.nodes, scenario[1][1])
Expand Down Expand Up @@ -385,6 +394,15 @@ function test_SimulatorSamplingScheme_with_noise()
return
end

function test_sample_noise()
@test SDDP.sample_noise(SDDP.Noise{Int}[]) === nothing
@test_throws(
ErrorException("Cumulative probability cannot be greater than 1.0."),
SDDP.sample_noise(SDDP.Noise.([1, 2], [0.5, 0.6])),
)
return
end

end # module

TestSamplingSchemes.runtests()
69 changes: 69 additions & 0 deletions test/user_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ function test_construct_Graph()
graph = SDDP.Graph(:root)
@test graph.root_node == :root
@test collect(keys(graph.nodes)) == [:root]
@test sprint(show, graph) == "Root\n root\nNodes\n {}\nArcs\n {}"
return
end

Expand Down Expand Up @@ -770,6 +771,74 @@ function test_no_stage_objective()
return
end

function test_validate_graph_errors()
graph = SDDP.LinearGraph(2)
SDDP.add_ambiguity_set(graph, [1])
@test_throws(
ErrorException(
"Belief partition [[1]] does not form a valid partition of the nodes in the graph.",
),
SDDP.PolicyGraph(graph; lower_bound = 0.0) do sp, t
@variable(sp, x, SDDP.State, initial_value = 1.0)
@constraint(sp, x.in == x.out)
end,
)
SDDP.add_ambiguity_set(graph, [0, 2])
@test_throws(
ErrorException(
"Belief partition [[1], [0, 2]] cannot contain the root node 0.",
),
SDDP.PolicyGraph(graph; lower_bound = 0.0) do sp, t
@variable(sp, x, SDDP.State, initial_value = 1.0)
@constraint(sp, x.in == x.out)
end,
)
return
end

function test_show_node()
model = SDDP.LinearPolicyGraph(; stages = 2, lower_bound = 0.0) do sp, t
@variable(sp, x, SDDP.State, initial_value = 1.0)
@constraint(sp, x.in == x.out)
SDDP.parameterize(sp, [1, 2]) do w
@stageobjective(sp, w * x.out)
return
end
end
@test sprint(show, model[1]) ==
"Node 1\n # State variables : 1\n # Children : 1\n # Noise terms : 2\n"
@test sprint(show, model[2]) ==
"Node 2\n # State variables : 1\n # Children : 0\n # Noise terms : 2\n"
return
end

function test_show_many_nodes()
model = SDDP.LinearPolicyGraph(; stages = 20, lower_bound = 0.0) do sp, t
@variable(sp, x, SDDP.State, initial_value = 1.0)
@constraint(sp, x.in == x.out)
end
@test sprint(show, model) ==
"A policy graph with 20 nodes.\n Node indices: 1, ..., 20\n"
return
end

function test_policy_graph_sense_error()
@test_throws(
ErrorException(
"The optimization sense must be `:Min` or `:Max`. It is MiniMization.",
),
SDDP.LinearPolicyGraph(;
stages = 2,
sense = :MiniMization,
lower_bound = 0.0,
) do sp, t
@variable(sp, x, SDDP.State, initial_value = 1.0)
@constraint(sp, x.in == x.out)
end,
)
return
end

end # module

TestUserInterface.runtests()
61 changes: 61 additions & 0 deletions test/visualization/value_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function test_ValueFunction_Min()
@test SDDP.evaluate(V1, Dict(:x => 1.0)) == (0.0, Dict(:x => 0.0))
SDDP.train(model; iteration_limit = 2, print_level = 0)
V1 = SDDP.ValueFunction(model[1])
@test sprint(show, V1) == "A value function for node 1"
for (xhat, yhat, pihat) in
[(0.0, 0.0, 0.0), (1.0, 2.0, 2.0), (2.0, 4.0, 2.0)]
@test SDDP.evaluate(V1, Dict(:x => xhat)) == (yhat, Dict(:x => pihat))
Expand Down Expand Up @@ -163,6 +164,66 @@ function test_ValuaeFunction_plot()
return
end

function test_ValueFunction_risk_measure_min()
model = SDDP.LinearPolicyGraph(;
stages = 2,
lower_bound = 0.0,
optimizer = HiGHS.Optimizer,
) do sp, t
@variable(sp, x >= 0, SDDP.State, initial_value = 1.5)
@constraint(sp, x.out == x.in)
SDDP.parameterize(sp, [1, 2]) do w
@stageobjective(sp, w * x.out)
return
end
return
end
SDDP.train(
model;
iteration_limit = 2,
print_level = 0,
risk_measure = SDDP.CVaR(0.25),
cut_type = SDDP.MULTI_CUT,
)
V1 = SDDP.ValueFunction(model[1])
for (xhat, yhat, pihat) in
[(0.0, 0.0, 0.0), (1.0, 2.0, 2.0), (2.0, 4.0, 2.0)]
@test SDDP.evaluate(V1, Dict(:x => xhat)) == (yhat, Dict(:x => pihat))
end
return
end

function test_ValueFunction_risk_measure_max()
model = SDDP.LinearPolicyGraph(;
stages = 2,
upper_bound = 10.0,
sense = :Max,
optimizer = HiGHS.Optimizer,
) do sp, t
@variable(sp, x >= 0, SDDP.State, initial_value = 1.5)
@constraint(sp, x.out == x.in)
SDDP.parameterize(sp, [1, 2]) do w
@stageobjective(sp, w * x.out)
return
end
return
end
SDDP.train(
model;
iteration_limit = 2,
print_level = 0,
risk_measure = SDDP.CVaR(0.25),
cut_type = SDDP.MULTI_CUT,
)
V1 = SDDP.ValueFunction(model[1])
for (xhat, yhat, pihat) in
[(0.0, 0.0, 1.0), (1.0, 1.0, 1.0), (2.0, 2.0, 1.0)]
@test SDDP.evaluate(V1, Dict(:x => xhat)) == (yhat, Dict(:x => pihat))
@test SDDP.evaluate(V1, Dict("x" => xhat)) == (yhat, Dict(:x => pihat))
end
return
end

end # module

TestValueFunctions.runtests()
6 changes: 6 additions & 0 deletions test/visualization/visualization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ module TestVisualization
using SDDP
using Test

import Plots

function runtests()
for name in names(@__MODULE__; all = true)
if startswith("$(name)", "test_")
Expand Down Expand Up @@ -62,6 +64,10 @@ function test_PublicationPlot()
[Dict{Symbol,Any}(:x => 2), Dict{Symbol,Any}(:x => 6)],
[Dict{Symbol,Any}(:x => 3), Dict{Symbol,Any}(:x => 4)],
]
plot = SDDP.publication_plot(simulations) do data
return data[:x]
end
@test plot isa Plots.Plot
data = SDDP.publication_data(simulations, [0.0, 0.25, 0.5, 1.0], d -> d[:x])
@test data == [1 4; 1.5 4.5; 2 5; 3 6]
for val in (-Inf, Inf, NaN)
Expand Down

0 comments on commit 0c99d6e

Please sign in to comment.