Skip to content

Commit f36fa81

Browse files
committed
add ddm calculation
1 parent 8808354 commit f36fa81

File tree

4 files changed

+128
-8
lines changed

4 files changed

+128
-8
lines changed

plotting/plot_ddm.jl

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using Dates, DataFrames
2+
using NEMStorageUnderUncertainty
3+
using CairoMakie
4+
using JLD2
5+
6+
function add_legend!(
7+
fig::Figure,
8+
x_loc,
9+
y_loc,
10+
value_colors::Vector{PolyElement},
11+
value_labels::Vector{String},
12+
value_title::String,
13+
)
14+
Legend(
15+
fig[y_loc, x_loc],
16+
[value_colors],
17+
[value_labels],
18+
[value_title];
19+
framevisible=false,
20+
)
21+
return nothing
22+
end
23+
24+
function _makie_plot_across_formulation(
25+
fig::Figure,
26+
plot_data::DataFrame,
27+
position::Int64,
28+
mw_capacity::Int64,
29+
formulations::Vector{String},
30+
colors,
31+
)
32+
title = string(round(Int, mw_capacity)) * " MW"
33+
ylabel = L"\frac{\textrm{NegRev}_{\textrm{Actual}} -\textrm{NegRev}_{\textrm{Forecast}}}{\textrm{Revenue}_{\textrm{Actual}}-\textrm{Revenue}_{\textrm{Forecast}}}"
34+
lookaheads = unique(plot_data.lookahead)
35+
ax = Axis(
36+
fig[position, 1];
37+
xticks=(1:length(lookaheads), string.(lookaheads) .* " min"),
38+
yticks=(range(0, 100; step=20), string.(range(0, 100; step=20))),
39+
title,
40+
ylabel=ylabel,
41+
xlabel="Lookahead (minutes)",
42+
)
43+
xs = [findfirst(x -> x == lk, lookaheads) for lk in plot_data.lookahead]
44+
groups = [findfirst(x -> x == f, formulations) for f in plot_data.label]
45+
barplot!(
46+
ax,
47+
xs,
48+
plot_data.ddm;
49+
dodge=groups,
50+
color=colors[groups],
51+
fillto=0.0,
52+
strokecolor=:gray,
53+
strokewidth=1,
54+
)
55+
return ylims!(ax, 0.0, 100.0)
56+
end
57+
58+
function plot_ddm_across_formulations(
59+
data_path::String, save_path::String
60+
)
61+
data_file = [f for f in readdir(data_path) if f == "ddm_vpl_vpi.jld2"][]
62+
all_data = load(joinpath(data_path, data_file))
63+
for (state, value) in pairs(all_data)
64+
energy = round(Int, unique(value.energy_capacity)[])
65+
state_data = all_data[state]
66+
plot_mw_capacities = (25, 100, 400)
67+
plot_lookaheads = ("5", "60", "240", "480", "900")
68+
fig = Figure(; resolution=(800, 1000))
69+
state_data.label = map(
70+
x -> NEMStorageUnderUncertainty.formulation_label_map[x], state_data.formulation
71+
)
72+
state_data.param = replace(state_data.param, missing => "")
73+
non_param_formulations = unique(state_data[state_data.param.=="", :formulation])
74+
param_formulations = unique(state_data[state_data.param.!="", :formulation])
75+
state_data[state_data.param.!="", :label] =
76+
state_data[state_data.param.!="", :label] .* " [" .*
77+
uppercasefirst.(string.(state_data[state_data.param.!="", :param])) .* "]"
78+
sims = sort(unique(state_data.label))
79+
colors = NEMStorageUnderUncertainty.generate_formulation_colors(
80+
param_formulations, non_param_formulations, sims
81+
)
82+
for (i, mw_capacity) in enumerate(plot_mw_capacities)
83+
energy = round(Int, unique(state_data.energy_capacity)[])
84+
mw_df = state_data[state_data.power_capacity.==mw_capacity, :]
85+
mw_df = mw_df[mw_df.lookahead.∈(plot_lookaheads,), :]
86+
mw_df.lookahead = parse.(Int64, mw_df.lookahead)
87+
_makie_plot_across_formulation(fig, mw_df, i, mw_capacity, sims, colors)
88+
end
89+
f_lg = [PolyElement(; polycolor=colors[i]) for i in 1:length(sims)]
90+
add_legend!(fig, 2, :, f_lg, sims, "Simulated formulation")
91+
title = "$energy MWh BESS - DDM - $state Prices, 2021"
92+
Label(fig[0, :]; text=title, fontsize=22, font="Source Sans Pro")
93+
filename = "$(state)_$(energy)_allformulations_ddm.pdf"
94+
save(joinpath(save_path, filename), fig; pt_per_unit=1)
95+
end
96+
end
97+
98+
99+
data_path = joinpath("results", "data")
100+
plot_path = joinpath("results", "plots", "neg_rev")
101+
if !ispath(plot_path)
102+
mkpath(plot_path)
103+
end
104+
105+
@assert ispath(data_path) "Results data not compiled. Run 'make compile_results'."
106+
107+
NEMStorageUnderUncertainty.set_project_plot_theme!()
108+
plot_ddm_across_formulations(
109+
joinpath("results", "data"), plot_path
110+
)
Binary file not shown.
Binary file not shown.

src/results.jl

+18-8
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ function _summarise_simulations(
105105
end
106106

107107
@doc raw"""
108-
Calculates values of perfect lookahead and information as absolute values (in AUD) and as
109-
a percentage of perfect foresight revenue.
108+
Calculates values of perfect lookahead and information (as absolute values in AUD and as
109+
a percentage of perfect foresight revenue), and the detrimental decision metric
110110
111111
**Value of perfect lookahead**: What is the additional benefit (revenue) that a participant
112112
could gain if they were to know exactly what the market prices will be in the *lookahead
@@ -118,6 +118,10 @@ participant could gain if they were to know exactly what the market prices will
118118
*over the entire year*
119119
* ``VPI = \textrm{Revenue}_\textrm{Perfect Foresight} - \textrm{Revenue}_\textrm{Forecast Data Simulation}``
120120
121+
**Detrimental decision metric**: What is the additional negative revenue (i.e. losses)
122+
incurred when using forecast market prices in the lookahead horizon as a percentage?
123+
* ``DDM = \frac{\textrm{NegRev}_{\textrm{Actual Data Simulation}} -\textrm{NegRev}_{\textrm{Forecast Data Simulation}}}{\textrm{Revenue}_{\textrm{Actual Data Simulation}}-\textrm{Revenue}_{\textrm{Forecast Data Simulation}}}``
124+
121125
N.B. This function assumes that the input `df` only has data that corresponds to a device
122126
of a particular `energy_capacity`.
123127
@@ -133,6 +137,7 @@ as a percentage of perfect foresight revenue.
133137
function calculate_vpl_vpi(df::DataFrame)
134138
(v_pl_abs, v_pi_abs) = (Float64[], Float64[])
135139
(v_pl_percentage, v_pi_percentage) = (Float64[], Float64[])
140+
ddms = Float64[]
136141
(power_caps, data) = (Float64[], String[])
137142
actual_caps = unique(df[df.data_type.=="actual", :power_capacity])
138143
forecast_caps = unique(df[df.data_type.=="forecast", :power_capacity])
@@ -145,21 +150,25 @@ function calculate_vpl_vpi(df::DataFrame)
145150
actual_mask = df.data_type .== "actual"
146151
forecast_mask = df.data_type .== "forecast"
147152
forecast_rev = df[cap_mask.&forecast_mask.&lk_mask, :revenue]
153+
forecast_neg_rev = df[cap_mask.&forecast_mask.&lk_mask, :neg_revenue]
148154
pf_rev = df[
149155
cap_mask.&forecast_mask.&(df.lookahead.=="Perfect Foresight"),
150156
:revenue,
151157
]
152158
pi_rev = df[cap_mask.&actual_mask.&lk_mask, :revenue]
159+
pi_neg_rev = df[cap_mask.&actual_mask.&lk_mask, :neg_revenue]
153160
v_pl = pi_rev - forecast_rev
154161
v_pi = pf_rev - forecast_rev
155162
v_pl_percentage_pf = @. v_pl / pf_rev * 100
156163
v_pi_percentage_pf = @. v_pi / pf_rev * 100
164+
ddm = @. (pi_neg_rev - forecast_neg_rev) / (pi_rev - forecast_rev) * 100
157165
push!(power_caps, cap)
158166
push!(data, lookahead)
159167
push!(v_pl_abs, v_pl[])
160168
push!(v_pi_abs, v_pi[])
161169
push!(v_pl_percentage, v_pl_percentage_pf[])
162170
push!(v_pi_percentage, v_pi_percentage_pf[])
171+
push!(ddms, ddm[])
163172
end
164173
end
165174
return DataFrame(
@@ -171,6 +180,7 @@ function calculate_vpl_vpi(df::DataFrame)
171180
:vpi_abs => v_pi_abs,
172181
:vpl_per => v_pl_percentage,
173182
:vpi_per => v_pi_percentage,
183+
:ddm => ddms,
174184
)
175185
end
176186

@@ -201,7 +211,7 @@ function calculate_vpl_vpi_across_scenarios(summary_folder::String)
201211
match(r"([A-Z]{2,3})_summary_results.jld2", file).captures[]
202212
)
203213

204-
@info "Calculating VPL and VPI for $state"
214+
@info "Calculating DDM, VPL and VPI for $state"
205215
summary_data = load(joinpath(summary_folder, file))
206216
vpl_vpi_data = DataFrame[]
207217
for (formulation, summary) in pairs(summary_data)
@@ -215,8 +225,8 @@ function calculate_vpl_vpi_across_scenarios(summary_folder::String)
215225
push!(vpl_vpi_data, vpl_vpi)
216226
end
217227
state_vpl_vpi = vcat(vpl_vpi_data...)
218-
@info "Saving VPL and VPI data for $state"
219-
jldopen(joinpath(summary_folder, "vpl_vpi.jld2"), "w"; compress=true) do f
228+
@info "Saving DDM, VPL and VPI data for $state"
229+
jldopen(joinpath(summary_folder, "ddm_vpl_vpi.jld2"), "w"; compress=true) do f
220230
f["$(state)"] = state_vpl_vpi
221231
end
222232
end
@@ -288,9 +298,9 @@ function calculate_summaries_and_vpl_vpi_across_scenarios(sim_folder::String)
288298
end
289299
end
290300
end
291-
vpl_vpi_file_name = joinpath(save_path, "vpl_vpi.jld2")
292-
if !isfile(vpl_vpi_file_name)
293-
@info "Calculating VPL and VPI across scenarios"
301+
ddm_vpl_vpi_file_name = joinpath(save_path, "ddm_vpl_vpi.jld2")
302+
if !isfile(ddm_vpl_vpi_file_name)
303+
@info "Calculating DDM, VPL and VPI across scenarios"
294304
calculate_vpl_vpi_across_scenarios(save_path)
295305
end
296306
return nothing

0 commit comments

Comments
 (0)