Skip to content

Commit 95ffc64

Browse files
committed
Improve state of charge plot
1 parent e1b4e1d commit 95ffc64

File tree

1 file changed

+49
-43
lines changed

1 file changed

+49
-43
lines changed

switch_model/generators/extensions/storage.py

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
storage, when to charge, energy accounting, etc.
88
"""
99
import math
10+
11+
import pandas as pd
1012
from scipy import fft
1113

1214
from pyomo.environ import *
@@ -444,68 +446,72 @@ def post_solve(instance, outdir):
444446
@graph(
445447
"state_of_charge",
446448
title="State of Charge Throughout the Year",
447-
supports_multi_scenario=True
449+
supports_multi_scenario=True,
450+
note="The daily charge/discharge amount is calculated as"
451+
" the difference between the maximum and minimum"
452+
" state of charge in a 1-day rolling window.\n"
453+
"The black line is the 14-day rolling mean of the state of charge."
448454
)
449455
def graph_state_of_charge(tools):
456+
# Each panel is a period and scenario
457+
panel_group = ["period", "scenario_name"]
458+
rolling_mean_window_size = '14D'
459+
450460
# Get the total state of charge per timepoint and scenario
451-
df = tools.get_dataframe("storage_dispatch")
452-
df = df.groupby(["timepoint", "scenario_name"], as_index=False)["StateOfCharge"].sum()
461+
soc = tools.get_dataframe("storage_dispatch.csv").rename({"StateOfCharge": "value"}, axis=1)
462+
soc = soc.groupby(["timepoint", "scenario_name"], as_index=False).value.sum()
463+
# Convert values to TWh
464+
soc.value /= 1e6
453465
# Add datetime information
454-
df = tools.transform.timestamp(df, key_col="timepoint")
466+
soc = tools.transform.timestamp(soc, key_col="timepoint")[panel_group + ["datetime", "value"]]
455467
# Count num rows
456-
num_periods = len(df["period"].unique())
468+
num_periods = len(soc["period"].unique())
457469

458-
# Get the total capacity per period and scenario
459-
capacity = tools.get_dataframe("storage_capacity.csv")
460-
capacity = capacity.groupby(["period", "scenario_name"], as_index=False)["OnlineEnergyCapacityMWh"].sum()
470+
# Used later
471+
grouped_soc = soc.set_index("datetime").groupby(panel_group, as_index=False)
461472

462-
# Add the capacity to our dataframe
463-
df = df.merge(
464-
capacity,
465-
on=["period", "scenario_name"],
466-
validate="many_to_one",
467-
how="left"
468-
)
473+
# Calculate the weekly SOC
474+
weekly_soc = grouped_soc.rolling(rolling_mean_window_size, center=True) \
475+
.value \
476+
.mean() \
477+
.reset_index()
469478

470-
# Convert values to TWh
471-
df["StateOfCharge"] /= 1e6
472-
df["OnlineEnergyCapacityMWh"] /= 1e6
479+
# Get the total capacity per period and scenario
480+
capacity = tools.get_dataframe("storage_capacity.csv")
481+
capacity = capacity.groupby(panel_group, as_index=False)["OnlineEnergyCapacityMWh"]\
482+
.sum()\
483+
.rename({"OnlineEnergyCapacityMWh": "value"}, axis=1)
484+
capacity.value /= 1e6
485+
capacity["type"] = "Total Energy Capacity"
473486

474487
# Add information regarding the diurnal cycle to the dataframe
475488
# Find the difference between the min and max for every day of the year
476-
group = df.groupby(["period", "scenario_name", tools.pd.Grouper(freq="D", key="datetime")])["StateOfCharge"]
477-
daily_size = (group.max() - group.min()).reset_index()
489+
group = grouped_soc.rolling('D', center=True).value
490+
daily_size = (group.max() - group.min()).reset_index().groupby(panel_group, as_index=False)
478491
# Find the mean between the difference of the min and max
479-
daily_size = daily_size.groupby(["period", "scenario_name"], as_index=False).mean().reset_index()
480-
# Add the mean to the dataframe under the name DiurnalCycle
481-
df = df.merge(
482-
daily_size.rename({"StateOfCharge": "DiurnalCycle"}, axis=1),
483-
on=["period", "scenario_name"],
484-
how="left"
485-
)
492+
avg_daily_size = daily_size.mean()[panel_group + ["value"]]
493+
avg_daily_size["type"] = "Mean Daily Charge/Discharge"
494+
max_daily_size = daily_size.max()[panel_group + ["value"]]
495+
max_daily_size["type"] = "Maximum Daily Charge/Discharge"
486496

487497
# Determine information for the labels
488-
y_axis_max = df["OnlineEnergyCapacityMWh"].max()
489-
label_offset = y_axis_max * 0.05
490-
label_x_pos = df["datetime"].median()
498+
y_axis_max = capacity.value.max()
499+
label_x_pos = soc["datetime"].median()
500+
501+
hlines = pd.concat([capacity, avg_daily_size, max_daily_size])
502+
491503
# For the max label
492-
df["label_position"] = df["OnlineEnergyCapacityMWh"] + label_offset
493-
df["label"] = df["OnlineEnergyCapacityMWh"].round(decimals=2)
494-
# For the diurnal cycle label
495-
df["diurnal_label"] = df["DiurnalCycle"].round(decimals=2)
496-
df["diurnal_label_pos"] = df["DiurnalCycle"] + label_offset
504+
hlines["label_pos"] = hlines.value + y_axis_max * 0.05
505+
hlines["label"] = hlines.value.round(decimals=2)
497506

498507
# Plot with plotnine
499508
pn = tools.pn
500-
plot = pn.ggplot(df, pn.aes(x="datetime", y="StateOfCharge")) \
501-
+ pn.geom_line() \
509+
plot = pn.ggplot(soc, pn.aes(x="datetime", y="value")) \
510+
+ pn.geom_line(color='gray') \
511+
+ pn.geom_line(data=weekly_soc, color='black') \
502512
+ pn.labs(y="State of Charge (TWh)", x="Time of Year") \
503-
+ pn.geom_hline(pn.aes(yintercept="OnlineEnergyCapacityMWh"), linetype="dashed", color='blue') \
504-
+ pn.geom_text(pn.aes(label="label", x=label_x_pos, y="label_position"), fontweight="light", size="10") \
505-
+ pn.geom_hline(pn.aes(yintercept="DiurnalCycle"), linetype="dashed", color='red') \
506-
+ pn.geom_text(pn.aes(label="diurnal_label", x=label_x_pos, y="diurnal_label_pos"), fontweight="light", size="10", color="red")
507-
508-
513+
+ pn.geom_hline(pn.aes(yintercept="value", label="label", color="type"), data=hlines, linetype="dashed") \
514+
+ pn.geom_text(pn.aes(label="label", x=label_x_pos, y="label_pos"), data=hlines, fontweight="light", size="10")
509515
tools.save_figure(by_scenario_and_period(tools, plot, num_periods).draw())
510516

511517

0 commit comments

Comments
 (0)