Skip to content

Commit e25b316

Browse files
committed
Simplify figure 2
1 parent 8882779 commit e25b316

File tree

4 files changed

+406
-22
lines changed

4 files changed

+406
-22
lines changed

papers/Martin_Staadecker_Value_of_LDES_and_Factors/LDES_paper_graphs/figure-1-baseline.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from papers.Martin_Staadecker_Value_of_LDES_and_Factors.LDES_paper_graphs.util import (
88
set_style,
9-
get_scenario,
9+
get_scenario, save_figure,
1010
)
1111

1212
tools = GraphTools([get_scenario("1342")], set_style=False)
@@ -205,6 +205,8 @@ def rolling_avg(df):
205205
plt.tight_layout()
206206
plt.tight_layout() # Twice to ensure it works properly, it's a bit weird at times
207207

208+
# %%
209+
save_figure("figure-1-baseline.png")
208210
# %% CALCULATIONS
209211

210212
import pandas as pd
Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
# %% IMPORTS AND SCENARIO DEFINITION
2+
import pandas as pd
3+
from matplotlib import pyplot as plt
4+
from matplotlib.colors import LinearSegmentedColormap
5+
from matplotlib.ticker import PercentFormatter
6+
7+
from switch_model.tools.graph.main import GraphTools
8+
from papers.Martin_Staadecker_Value_of_LDES_and_Factors.LDES_paper_graphs.util import (
9+
get_scenario,
10+
set_style, save_figure,
11+
)
12+
13+
custom_color_map = LinearSegmentedColormap.from_list(
14+
"custom_color_map", ["#E0A4AA", "#B9646B", "#4D0409"]
15+
)
16+
17+
# GET DATA FOR W/S RATIO
18+
STORAGE_BINS = (float("-inf"), 10, 20, float("inf"))
19+
STORAGE_LABELS = GraphTools.create_bin_labels(STORAGE_BINS)
20+
for i in range(len(STORAGE_LABELS)):
21+
STORAGE_LABELS[i] = STORAGE_LABELS[i] + "h Storage (GW)"
22+
23+
# Define tools for wind to solar ratio set
24+
baseline_ws_ratio = 0.187
25+
# Get Graph tools
26+
tools_ws_ratio = GraphTools(
27+
scenarios=[
28+
get_scenario("WS10", 0.0909),
29+
get_scenario("WS018", 0.15),
30+
get_scenario("1342", baseline_ws_ratio),
31+
get_scenario("WS027", 0.21),
32+
get_scenario("WS033", 0.25),
33+
get_scenario("WS043", 0.3),
34+
get_scenario("WS066", 0.4),
35+
get_scenario("WS100", 0.5),
36+
get_scenario("WS150", 0.6),
37+
# get_scenario("WS233", 0.7), # Removed since results are invalid
38+
# get_scenario("WS500", 0.833), # Removed since results are misleading
39+
], set_style=False
40+
)
41+
tools_ws_ratio.pre_graphing(multi_scenario=True)
42+
43+
# Define tools for hydro set
44+
tools_hydro = GraphTools(
45+
scenarios=[
46+
get_scenario("H25", 0),
47+
get_scenario("H025", 0.25),
48+
get_scenario("H050", 0.5),
49+
get_scenario("H065", 0.65),
50+
get_scenario("H085", 0.85),
51+
get_scenario("1342", 1),
52+
], set_style=False
53+
)
54+
tools_hydro.pre_graphing(multi_scenario=True)
55+
56+
# GET TX DATA
57+
tools_tx = GraphTools(
58+
scenarios=[
59+
get_scenario("T4", "No Tx Build Costs\n(No Tx Congestion)"),
60+
get_scenario("1342", "Baseline"),
61+
get_scenario("T5", "10x Tx\nBuild Costs"),
62+
], set_style=False
63+
)
64+
tools_tx.pre_graphing(multi_scenario=True)
65+
66+
baseline_energy_cost = 22.43
67+
68+
# GET COST DATA
69+
tools_cost = GraphTools(
70+
scenarios=[
71+
get_scenario("C21", 0.5),
72+
get_scenario("C18", 1),
73+
get_scenario("C22", 2),
74+
get_scenario("C23", 5),
75+
get_scenario("C26", 7),
76+
get_scenario("C17", 10),
77+
get_scenario("C24", 15),
78+
get_scenario("1342", baseline_energy_cost),
79+
get_scenario("C25", 40),
80+
get_scenario("C19", 70),
81+
get_scenario("C20", 102)
82+
], set_style=False
83+
)
84+
tools_cost.pre_graphing(multi_scenario=True)
85+
86+
87+
def get_data(tools, normalize_to_baseline=None):
88+
storage = tools.get_dataframe("storage_capacity.csv")
89+
duration = storage.copy()
90+
duration = duration[duration["OnlinePowerCapacityMW"] != 0]
91+
duration["duration"] = (
92+
duration["OnlineEnergyCapacityMWh"] / duration["OnlinePowerCapacityMW"]
93+
)
94+
duration = duration[["scenario_index", "duration", "OnlinePowerCapacityMW"]]
95+
duration["Duration (h)"] = pd.cut(
96+
duration.duration, bins=STORAGE_BINS, labels=STORAGE_LABELS
97+
)
98+
duration = duration.groupby(
99+
["scenario_index", "Duration (h)"]
100+
).OnlinePowerCapacityMW.sum()
101+
duration /= 10 ** 3
102+
duration = duration.unstack()
103+
duration.index = duration.index.map(tools.get_scenario_name)
104+
105+
storage = storage[["scenario_index", "OnlineEnergyCapacityMWh"]]
106+
storage = storage.groupby("scenario_index").sum()
107+
storage.index = storage.index.map(tools.get_scenario_name)
108+
storage *= 1e-6
109+
storage.columns = ["Storage Energy Capacity (TWh)"]
110+
111+
# Calculate transmission
112+
tx = tools.get_dataframe(
113+
"transmission.csv",
114+
usecols=["BuildTx", "trans_length_km", "scenario_index"],
115+
convert_dot_to_na=True,
116+
).fillna(0)
117+
tx["BuildTx"] *= tx["trans_length_km"]
118+
tx["BuildTx"] *= 1e-6
119+
tx = (
120+
tx.groupby("scenario_index", as_index=False)["BuildTx"]
121+
.sum()
122+
.set_index("scenario_index")
123+
)
124+
125+
tx = tx.rename({"BuildTx": "New Tx"}, axis=1)
126+
tx.index = tx.index.map(tools.get_scenario_name)
127+
128+
cap = tools.get_dataframe("gen_cap.csv")
129+
cap = tools.transform.gen_type(cap)
130+
cap = cap.groupby(["scenario_index", "gen_type"], as_index=False)[
131+
"GenCapacity"
132+
].sum()
133+
cap = cap.pivot(columns="gen_type", index="scenario_index", values="GenCapacity")
134+
cap *= 1e-3 # Convert to GW
135+
cap = cap.rename_axis("Technology", axis=1).rename_axis(None)
136+
cap = cap[["Wind", "Solar"]]
137+
cap.index = cap.index.map(tools.get_scenario_name)
138+
139+
# Make it a percent change compared to the baseline
140+
if normalize_to_baseline is not None:
141+
tx = (tx / tx.loc[normalize_to_baseline])
142+
cap = (cap / cap.loc[normalize_to_baseline])
143+
storage = (storage / storage.loc[normalize_to_baseline])
144+
duration = (duration / duration.sum(axis=1).loc[normalize_to_baseline])
145+
146+
return duration, tx, cap, storage
147+
148+
149+
# %% DEFINE FIGURE AND PLOTTING FUNCTIONS
150+
set_style()
151+
plt.close()
152+
fig = plt.figure()
153+
fig.set_size_inches(6.850394, 6.850394)
154+
155+
# Define axes
156+
ax_tl = fig.add_subplot(2, 2, 1)
157+
ax_tr = fig.add_subplot(2, 2, 2, sharey=ax_tl)
158+
ax_bl = fig.add_subplot(2, 2, 3)
159+
ax_br = fig.add_subplot(2, 2, 4, sharey=ax_bl)
160+
Y_LIM_BASE = 1.85
161+
ax_tl.set_ylim(0, Y_LIM_BASE)
162+
ax_bl.set_ylim(0, Y_LIM_BASE)
163+
ax_tl.yaxis.set_major_formatter(PercentFormatter(xmax=1))
164+
ax_bl.yaxis.set_major_formatter(PercentFormatter(xmax=1))
165+
166+
167+
# def create_secondary_y_axis(ax, include_label, y_lim, y_label, color="grey", offset=-0.25):
168+
# rax = ax.twinx()
169+
# rax.set_ylim(0, y_lim)
170+
# if include_label:
171+
# rax.spines["left"].set_position(("axes", offset))
172+
# rax.yaxis.set_label_position("left")
173+
# rax.yaxis.tick_left()
174+
# rax.tick_params(top=False, bottom=False, right=False, left=True, which="both")
175+
# rax.spines["left"].set_color(color)
176+
# rax.set_ylabel(y_label)
177+
# else:
178+
# rax.tick_params(top=False, bottom=False, right=False, left=False, which="both")
179+
# rax.set_yticklabels([])
180+
# return rax
181+
182+
183+
# y_lim = (Y_LIM_BASE) * 200
184+
# y_label = "Storage Power Capacity (GW)"
185+
# c = "tab:red"
186+
# rax_top_left = create_secondary_y_axis(ax_tl, True, y_lim, y_label, c)
187+
# rax_top_right = create_secondary_y_axis(ax_tr, False, y_lim, y_label, c)
188+
# rax_bottom_left = create_secondary_y_axis(ax_bl, True, y_lim, y_label, c)
189+
# rax_bottom_right = create_secondary_y_axis(ax_br, False, y_lim, y_label, c)
190+
191+
192+
# y_lim = Y_LIM_BASE / 50
193+
# y_label = "Energy Capacity (TWh)"
194+
# c = "green"
195+
# rrax_tl = create_secondary_y_axis(ax_tl, True, y_lim, y_label, c, offset=-0.4)
196+
# rrax_tr = create_secondary_y_axis(ax_tr, False, y_lim, y_label, c, offset=-0.4)
197+
# rrax_bl = create_secondary_y_axis(ax_bl, True, y_lim, y_label, c, offset=-0.4)
198+
# rrax_br = create_secondary_y_axis(ax_br, False, y_lim, y_label, c, offset=-0.4)
199+
#
200+
# y_lim = Y_LIM_BASE * 2
201+
# y_label = "Solar Power Capacity (GW)"
202+
# c = tools_ws_ratio.get_colors()["Solar"]
203+
# rrrax_tl = create_secondary_y_axis(ax_tl, True, y_lim, y_label, c, offset=-0.6)
204+
# rrrax_tr = create_secondary_y_axis(ax_tr, False, y_lim, y_label, c, offset=-0.6)
205+
# rrrax_bl = create_secondary_y_axis(ax_bl, True, y_lim, y_label, c, offset=-0.6)
206+
# rrrax_br = create_secondary_y_axis(ax_br, False, y_lim, y_label, c, offset=-0.6)
207+
208+
209+
# %% DEFINE PLOTTING CODE
210+
def plot_panel(ax, rax, rrax, rrrax, data, title=""):
211+
duration, tx, cap, storage = data
212+
lw = 1
213+
s = 3
214+
colors = tools_ws_ratio.get_colors()
215+
duration.index.name = None
216+
storage.index.name = None
217+
duration.plot(ax=ax, colormap=custom_color_map, legend=False, kind="area", zorder=1.5, alpha=0.5, linewidth=0)
218+
# duration.sum(axis=1).plot(ax=ax, marker=".", color="red", label="All Storage (GW)", legend=False,
219+
# linewidth=lw,
220+
# markersize=s)
221+
ax.plot(tx, marker=".", color="tab:red", label="Built Transmission", linewidth=lw, markersize=s)
222+
cap["Wind"].plot(ax=ax, marker=".", color=colors, legend=False, linewidth=lw, markersize=s)
223+
cap["Solar"].plot(ax=ax, marker=".", color=colors, legend=False, linewidth=lw, markersize=s)
224+
storage.plot(ax=ax, marker=".", color="green", linewidth=lw, markersize=s,
225+
legend=False)
226+
ax.set_ylabel("Percent of baseline capacity")
227+
ax.set_title(title)
228+
229+
230+
# %% PLOT WIND TO SOLAR PENETRATION
231+
232+
data_ws = get_data(tools_ws_ratio, normalize_to_baseline=baseline_ws_ratio)
233+
234+
ax = ax_tl
235+
# rax = rax_top_left
236+
ax.tick_params(top=False, bottom=True, right=False, left=True, which="both")
237+
238+
plot_panel(ax, None, None, None, data_ws, "Set A: Varying Wind-vs-Solar Share")
239+
240+
ax.set_xticks([0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
241+
ax.set_xticklabels(["90%-10%\nSolar-Wind", "", "70%-30%\nSolar-Wind", "", "50%-50%\nSolar-Wind", ""])
242+
ax.axvline(baseline_ws_ratio, linestyle="dotted", color="dimgrey")
243+
ax.text(baseline_ws_ratio - 0.02, 0.1, "Baseline", rotation=90, color="dimgrey")
244+
245+
fig.legend(loc="lower center", ncol=4)
246+
247+
# %% PLOT HYDRO
248+
data_hy = get_data(tools_hydro, normalize_to_baseline=1)
249+
ax = ax_tr
250+
# rax = rax_top_right
251+
plot_panel(ax, None, None, None, data_hy, "Set B: Reducing Hydropower Generation")
252+
ax.tick_params(top=False, bottom=True, right=False, left=False, which="both")
253+
ax.set_xticks([0, 0.5, 1])
254+
ax.set_xticks([0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9], minor=True)
255+
ax.set_xticklabels(["No\nhydropower", "50%\nhydropower", "Baseline\nHydropower"])
256+
257+
# %% PLOT TX
258+
ax = ax_bl
259+
# rax = rax_bottom_left
260+
data_tx = get_data(tools_tx, normalize_to_baseline="Baseline")
261+
ax.set_xticks([0, 1, 2])
262+
plot_panel(ax, None, None, None, data_tx, "Set C: Varying Transmission Build Costs")
263+
# %% PLOT COSTS
264+
ax = ax_br
265+
# rax = rax_bottom_right
266+
data_cost = get_data(tools_cost, normalize_to_baseline=baseline_energy_cost)
267+
plot_panel(ax, None, None, None, data_cost, "Set D: Varying Storage Energy Costs")
268+
ax.set_xscale("log")
269+
ax.tick_params(top=False, bottom=True, right=False, left=False, which="both")
270+
ax.set_xticks([1, 10, 100])
271+
ax.set_xticks(
272+
[
273+
0.5,
274+
0.6,
275+
0.7,
276+
0.8,
277+
0.9,
278+
1,
279+
2,
280+
3,
281+
4,
282+
5,
283+
6,
284+
7,
285+
8,
286+
9,
287+
10,
288+
20,
289+
30,
290+
40,
291+
50,
292+
60,
293+
70,
294+
80,
295+
90,
296+
],
297+
minor=True,
298+
)
299+
ax.set_xticklabels(["1\n$/kWh", "10\n$/kWh", "100\n$/kWh"])
300+
ax.set_xlabel("(log scale)")
301+
ax.axvline(baseline_energy_cost, linestyle="dotted", color="dimgrey")
302+
ax.text(baseline_energy_cost - 4, 0.1, "Baseline", rotation=90, color="dimgrey")
303+
304+
plt.subplots_adjust(left=0.08, right=0.97, top=0.95, wspace=0.05)
305+
306+
# %% SAVE FIGURE
307+
save_figure("figure-2-analysis-of-4-factors.png")
308+
# %% MAX POWER DURATION
309+
df = tools_ws_ratio.get_dataframe("storage_capacity.csv")
310+
df = df[df.scenario_name == 0.833]
311+
df["duration"] = df["duration"] = (
312+
df["OnlineEnergyCapacityMWh"] / df["OnlinePowerCapacityMW"]
313+
)
314+
df = df[["load_zone", "duration", "OnlinePowerCapacityMW"]]
315+
df.sort_values("duration")
316+
# %% HYDRO POWER impact
317+
en = data_hy[3].copy() * 1000
318+
pw = data_hy[0].sum(axis=1).copy()
319+
pw, en
320+
321+
# %% HYDRO power total capacity
322+
df = tools_hydro.get_dataframe("dispatch_annual_summary.csv")
323+
scenario = 1
324+
# scenario = 0.5
325+
df = df[df.scenario_name == scenario]
326+
df = tools_hydro.transform.gen_type(df)
327+
df = df[df.gen_type != "Storage"]
328+
df = df.groupby("gen_type").Energy_GWh_typical_yr.sum()
329+
df *= 1e-3
330+
df
331+
df.loc["Hydro"] / df.sum()
332+
# %%
333+
data_tx
334+
# %% Storage duration in 50% hydro
335+
df = tools_hydro.get_dataframe("dispatch_zonal_annual_summary.csv")
336+
df = df[df.scenario_name == 1]
337+
df = tools_hydro.transform.gen_type(df)
338+
df = df[["gen_load_zone", "gen_type", "Energy_GWh_typical_yr"]].set_index("gen_load_zone")
339+
df_sum = df.groupby("gen_load_zone").Energy_GWh_typical_yr.sum()
340+
df_sum = df_sum.rename("total")
341+
df = df.join(df_sum)
342+
cutoff = 0.5
343+
df["percent"] = df["Energy_GWh_typical_yr"] / df["total"]
344+
df = df[["percent", "gen_type"]].reset_index()
345+
df = df[df.gen_type == "Hydro"]
346+
df = df[df.percent > cutoff]
347+
valid_load_zones = df["gen_load_zone"]
348+
349+
df = tools_hydro.get_dataframe("storage_capacity.csv")
350+
# df = df[df.scenario_name == 0.5]
351+
df = df[["load_zone", "scenario_name", "OnlinePowerCapacityMW", "OnlineEnergyCapacityMWh"]]
352+
df = df[df.load_zone.isin(valid_load_zones)]
353+
df = df.groupby("scenario_name").sum()
354+
df["OnlineEnergyCapacityMWh"] / df["OnlinePowerCapacityMW"]
355+
# valid_load_zones
356+
357+
# %% TX Change
358+
df = data_tx[2]
359+
df
360+
# %% COSTS
361+
df = data_cost
362+
table = 1
363+
df[table], (data_cost[table] / data_cost[table].loc[22.43] - 1) * 100
364+
# %%
365+
df = tools_cost.get_dataframe("storage_capacity.csv")
366+
df["duration"] = df["OnlineEnergyCapacityMWh"] / df["OnlinePowerCapacityMW"]
367+
df = df.groupby("scenario_name").duration.max()
368+
df
369+
# %%
370+
df = tools_cost.get_dataframe("storage_capacity.csv")
371+
df["duration"] = df["OnlineEnergyCapacityMWh"] / df["OnlinePowerCapacityMW"]
372+
total_power = df.groupby("scenario_name").OnlinePowerCapacityMW.sum()
373+
total_energy = df.groupby("scenario_name").OnlineEnergyCapacityMWh.sum()
374+
total_energy / total_power

0 commit comments

Comments
 (0)