Skip to content

Commit 636d330

Browse files
pesapstaadecker
authored andcommitted
Merge pull request #86 from staadecker/improve-plots
More plot improvements
2 parents 6432404 + f60e77b commit 636d330

File tree

6 files changed

+429
-436
lines changed

6 files changed

+429
-436
lines changed

switch_model/balancing/load_zones.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,9 @@ def get_component_per_year(m, z, p, component):
286286
@graph(
287287
"energy_balance_duals",
288288
title="Energy balance duals per period",
289-
note="Note: Outliers and zero-valued duals are ignored."
289+
note="Note: Outliers and zero-valued duals are ignored.",
290290
)
291-
def graph(tools):
291+
def graph_energy_balance(tools):
292292
load_balance = tools.get_dataframe("load_balance.csv")
293293
load_balance = tools.transform.timestamp(load_balance)
294294
load_balance["energy_balance_duals"] = (
@@ -303,22 +303,50 @@ def graph(tools):
303303
# Don't include the zero-valued duals
304304
load_balance = load_balance.replace(0, tools.np.nan)
305305
if load_balance.count().sum() != 0:
306-
<<<<<<< HEAD
307-
ax = tools.get_axes(
308-
"energy_balance_duals",
309-
title="Energy balance duals per period",
310-
note="Note: Outliers and zero-valued duals are ignored.",
311-
)
312306
load_balance.plot.box(
313-
ax=ax,
307+
ax=tools.get_axes(),
314308
xlabel="Period",
315309
ylabel="Energy balance duals (cents/kWh)",
316310
showfliers=False,
317-
=======
318-
load_balance.plot.box(
319-
ax=tools.get_axes(),
320-
xlabel='Period',
321-
ylabel='Energy balance duals (cents/kWh)',
322-
showfliers=False
323-
>>>>>>> b3590fdb (Redesign graphing API)
324311
)
312+
313+
314+
@graph("daily_demand", title="Total daily demand", supports_multi_scenario=True)
315+
def demand(tools):
316+
df = tools.get_dataframe("loads.csv", from_inputs=True, drop_scenario_info=False)
317+
df = df.groupby(["TIMEPOINT", "scenario_name"], as_index=False).sum()
318+
df = tools.transform.timestamp(df, key_col="TIMEPOINT", use_timepoint=True)
319+
df = df.groupby(
320+
["season", "hour", "scenario_name", "time_row"], as_index=False
321+
).mean()
322+
df["zone_demand_mw"] /= 1e3
323+
pn = tools.pn
324+
325+
plot = (
326+
pn.ggplot(df)
327+
+ pn.geom_line(pn.aes(x="hour", y="zone_demand_mw", color="scenario_name"))
328+
+ pn.facet_grid("time_row ~ season")
329+
+ pn.labs(x="Hour (PST)", y="Demand (GW)", color="Scenario")
330+
)
331+
tools.save_figure(plot.draw())
332+
333+
334+
@graph("demand", title="Total demand", supports_multi_scenario=True)
335+
def yearly_demand(tools):
336+
df = tools.get_dataframe("loads.csv", from_inputs=True, drop_scenario_info=False)
337+
df = df.groupby(["TIMEPOINT", "scenario_name"], as_index=False).sum()
338+
df = tools.transform.timestamp(df, key_col="TIMEPOINT", use_timepoint=True)
339+
df["zone_demand_mw"] *= df["tp_duration"] / 1e3
340+
df["day"] = df["datetime"].dt.day_of_year
341+
df = df.groupby(["day", "scenario_name", "time_row"], as_index=False)[
342+
"zone_demand_mw"
343+
].sum()
344+
pn = tools.pn
345+
346+
plot = (
347+
pn.ggplot(df)
348+
+ pn.geom_line(pn.aes(x="day", y="zone_demand_mw", color="scenario_name"))
349+
+ pn.facet_grid("time_row ~ .")
350+
+ pn.labs(x="Day of Year", y="Demand (GW)", color="Scenario")
351+
)
352+
tools.save_figure(plot.draw())

switch_model/generators/core/build.py

Lines changed: 8 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -720,22 +720,7 @@ def post_solve(m, outdir):
720720
)
721721

722722

723-
<<<<<<< HEAD
724-
def graph(tools):
725-
graph_capacity(tools)
726-
graph_buildout_per_tech(tools)
727-
728-
729-
def compare(tools):
730-
graph_buildout(tools)
731-
732-
733-
=======
734-
@graph(
735-
"generation_capacity_per_period",
736-
title="Online Generation Capacity Per Period"
737-
)
738-
>>>>>>> b3590fdb (Redesign graphing API)
723+
@graph("generation_capacity_per_period", title="Online Generation Capacity Per Period")
739724
def graph_capacity(tools):
740725
# Load gen_cap.csv
741726
gen_cap = tools.get_dataframe("gen_cap.csv")
@@ -768,33 +753,25 @@ def graph_capacity(tools):
768753

769754
# Plot
770755
# Get a new set of axis to create a breakdown of the generation capacity
771-
<<<<<<< HEAD
772-
ax = tools.get_axes(
773-
out="generation_capacity_per_period",
774-
title="Online generating capacity by period",
775-
)
776756
capacity_df.plot(
777757
kind="bar",
778-
ax=ax,
779-
=======
780-
capacity_df.plot(
781-
kind='bar',
782758
ax=tools.get_axes(),
783-
>>>>>>> b3590fdb (Redesign graphing API)
784759
stacked=True,
785760
ylabel="Capacity Online (GW)",
786761
xlabel="Period",
787762
color=tools.get_colors(len(capacity_df.index)),
788763
)
789764

765+
tools.bar_label()
766+
790767

791768
@graph(
792769
"buildout_gen_per_period",
793770
title="Built Capacity per Period",
794-
supports_multi_scenario=True
771+
supports_multi_scenario=True,
795772
)
796773
def graph_buildout(tools):
797-
build_gen = tools.get_dataframe("BuildGen.csv")
774+
build_gen = tools.get_dataframe("BuildGen.csv", dtype={"GEN_BLD_YRS_1": str})
798775
build_gen = build_gen.rename(
799776
{
800777
"GEN_BLD_YRS_1": "GENERATION_PROJECT",
@@ -804,13 +781,7 @@ def graph_buildout(tools):
804781
axis=1,
805782
)
806783
build_gen = tools.transform.build_year(build_gen)
807-
<<<<<<< HEAD
808-
gen = tools.get_dataframe(
809-
"generation_projects_info", from_inputs=True, all_scenarios=True
810-
)
811-
=======
812784
gen = tools.get_dataframe("generation_projects_info", from_inputs=True)
813-
>>>>>>> b3590fdb (Redesign graphing API)
814785
gen = tools.transform.gen_type(gen)
815786
gen = gen[["GENERATION_PROJECT", "gen_type", "scenario_name"]]
816787
build_gen = build_gen.merge(
@@ -845,33 +816,22 @@ def graph_buildout(tools):
845816

846817
# Plot
847818
# Get a new set of axis to create a breakdown of the generation capacity
848-
<<<<<<< HEAD
849-
ax = tools.get_axes(out="buildout_per_period", title="Built capacity per period")
850819
build_gen.plot(
851820
kind="bar",
852-
ax=ax,
853-
=======
854-
build_gen.plot(
855-
kind='bar',
856821
ax=tools.get_axes(),
857-
>>>>>>> b3590fdb (Redesign graphing API)
858822
stacked=True,
859823
ylabel="Capacity Online (GW)",
860824
xlabel="Period",
861825
color=tools.get_colors(len(build_gen.index)),
862826
)
863-
<<<<<<< HEAD
864-
865-
=======
866-
>>>>>>> b3590fdb (Redesign graphing API)
867827

868828

869829
@graph(
870830
"gen_buildout_per_tech_period",
871831
title="Buildout relative to max allowed for period",
872832
note="\nNote 1: This graph excludes predetermined buildout and projects that have no capacity limit."
873-
"\nTechnologies that contain projects with no capacity limit are marked by a * and their graphs may"
874-
"be misleading."
833+
"\nTechnologies that contain projects with no capacity limit are marked by a * and their graphs may"
834+
"be misleading.",
875835
)
876836
def graph_buildout_per_tech(tools):
877837
# Load gen_cap.csv
@@ -920,32 +880,16 @@ def graph_buildout_per_tech(tools):
920880
# Set the name of the legend.
921881
df = df.rename_axis("Type", axis="columns")
922882
# Add a * to tech
923-
<<<<<<< HEAD
924883
df = df.rename(
925884
lambda c: f"{c}*" if c in unlimited_gen_types.values else c, axis="columns"
926885
)
927-
# Get axes to graph on
928-
ax = tools.get_axes(
929-
out="gen_buildout_per_tech_no_pred",
930-
title="Buildout relative to max allowed for period",
931-
note="\nNote 1: This graph excludes predetermined buildout and projects that have no capacity limit."
932-
"\nTechnologies that contain projects with no capacity limit are marked by a * and their graphs may"
933-
"be misleading.",
934-
)
935-
=======
936-
df = df.rename(lambda c: f"{c}*" if c in unlimited_gen_types.values else c, axis='columns')
937-
>>>>>>> b3590fdb (Redesign graphing API)
938886
# Plot
939887
colors = tools.get_colors()
940888
if colors is not None:
941889
# Add the same colors but with a * to support our legend.
942890
colors.update({f"{k}*": v for k, v in colors.items()})
943-
<<<<<<< HEAD
944-
df.plot(ax=ax, kind="line", color=colors, xlabel="Period", marker="x")
945-
=======
946891
ax = tools.get_axes()
947-
df.plot(ax=ax, kind='line', color=colors, xlabel='Period', marker="x")
948-
>>>>>>> b3590fdb (Redesign graphing API)
892+
df.plot(ax=ax, kind="line", color=colors, xlabel="Period", marker="x")
949893
# Set the y-axis to use percent
950894
ax.yaxis.set_major_formatter(tools.mplt.ticker.PercentFormatter(1.0))
951895
# Horizontal line at 100%

switch_model/generators/core/dispatch.py

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,6 @@ def graph_hourly_curtailment(tools):
774774
@graph(
775775
"total_dispatch",
776776
title="Total dispatched electricity",
777-
is_long=True,
778777
)
779778
def graph_total_dispatch(tools):
780779
# ---------------------------------- #
@@ -829,6 +828,134 @@ def graph_total_dispatch(tools):
829828
>>>>>>> b3590fdb (Redesign graphing API)
830829
)
831830

831+
tools.bar_label()
832+
833+
@graph(
834+
"energy_balance",
835+
title="Energy Balance For Every Month",
836+
supports_multi_scenario=True,
837+
is_long=True
838+
)
839+
def energy_balance(tools):
840+
# Get dispatch dataframe
841+
cols = ["timestamp", "gen_tech", "gen_energy_source", "DispatchGen_MW", "scenario_name", "scenario_index",
842+
"Curtailment_MW"]
843+
df = tools.get_dataframe("dispatch.csv", drop_scenario_info=False)[cols]
844+
df = tools.transform.gen_type(df)
845+
846+
# Rename and add needed columns
847+
df["Dispatch Limit"] = df["DispatchGen_MW"] + df["Curtailment_MW"]
848+
df = df.drop("Curtailment_MW", axis=1)
849+
df = df.rename({"DispatchGen_MW": "Dispatch"}, axis=1)
850+
# Sum dispatch across all the projects of the same type and timepoint
851+
key_columns = ["timestamp", "gen_type", "scenario_name", "scenario_index"]
852+
df = df.groupby(key_columns, as_index=False).sum()
853+
df = df.melt(id_vars=key_columns, value_vars=["Dispatch", "Dispatch Limit"], var_name="Type")
854+
df = df.rename({"gen_type": "Source"}, axis=1)
855+
856+
discharge = df[(df["Source"] == "Storage") & (df["Type"] == "Dispatch")].drop(["Source", "Type"], axis=1).rename(
857+
{"value": "discharge"}, axis=1)
858+
859+
# Get load dataframe
860+
load = tools.get_dataframe("load_balance.csv", drop_scenario_info=False)
861+
load = load.drop("normalized_energy_balance_duals_dollar_per_mwh", axis=1)
862+
863+
# Sum load across all the load zones
864+
key_columns = ["timestamp", "scenario_name", "scenario_index"]
865+
load = load.groupby(key_columns, as_index=False).sum()
866+
867+
# Subtract storage dispatch from generation and add it to the storage charge to get net flow
868+
load = load.merge(
869+
discharge,
870+
how="left",
871+
on=key_columns,
872+
validate="one_to_one"
873+
)
874+
load["ZoneTotalCentralDispatch"] -= load["discharge"]
875+
load["StorageNetCharge"] += load["discharge"]
876+
load = load.drop("discharge", axis=1)
877+
878+
# Rename and convert from wide to long format
879+
load = load.rename({
880+
"ZoneTotalCentralDispatch": "Total Generation (excl. storage discharge)",
881+
"TXPowerNet": "Transmission Losses",
882+
"StorageNetCharge": "Storage Net Flow",
883+
"zone_demand_mw": "Demand",
884+
}, axis=1).sort_index(axis=1)
885+
load = load.melt(id_vars=key_columns, var_name="Source")
886+
load["Type"] = "Dispatch"
887+
888+
# Merge dispatch contributions with load contributions
889+
df = pd.concat([load, df])
890+
891+
# Add the timestamp information and make period string to ensure it doesn't mess up the graphing
892+
df = tools.transform.timestamp(df).astype({"period": str})
893+
894+
# Convert to TWh (incl. multiply by timepoint duration)
895+
df["value"] *= df["tp_duration"] / 1e6
896+
897+
FREQUENCY = "1W"
898+
899+
def groupby_time(df):
900+
return df.groupby([
901+
"scenario_name",
902+
"period",
903+
"Source",
904+
"Type",
905+
tools.pd.Grouper(key="datetime", freq=FREQUENCY, origin="start")
906+
])["value"]
907+
908+
df = groupby_time(df).sum().reset_index()
909+
910+
# Get the state of charge data
911+
soc = tools.get_dataframe("StateOfCharge.csv", dtype={"STORAGE_GEN_TPS_1": str}, drop_scenario_info=False)
912+
soc = soc.rename({"STORAGE_GEN_TPS_2": "timepoint", "StateOfCharge": "value"}, axis=1)
913+
# Sum over all the projects that are in the same scenario with the same timepoint
914+
soc = soc.groupby(["timepoint", "scenario_name"], as_index=False).sum()
915+
soc["Source"] = "State Of Charge"
916+
soc["value"] /= 1e6 # Convert to TWh
917+
918+
# Group by time
919+
soc = tools.transform.timestamp(soc, use_timepoint=True, key_col="timepoint").astype({"period": str})
920+
soc["Type"] = "Dispatch"
921+
soc = groupby_time(soc).mean().reset_index()
922+
923+
# Add state of charge to dataframe
924+
df = pd.concat([df, soc])
925+
# Add column for day since that's what we really care about
926+
df["day"] = df["datetime"].dt.dayofyear
927+
928+
# Plot
929+
# Get the colors for the lines
930+
colors = tools.get_colors()
931+
colors.update({
932+
"Transmission Losses": "brown",
933+
"Storage Net Flow": "cadetblue",
934+
"Demand": "black",
935+
"Total Generation (excl. storage discharge)": "black",
936+
"State Of Charge": "green"
937+
})
938+
939+
# plot
940+
num_periods = df["period"].nunique()
941+
pn = tools.pn
942+
plot = pn.ggplot(df) + \
943+
pn.geom_line(pn.aes(x="day", y="value", color="Source", linetype="Type")) + \
944+
pn.facet_grid("period ~ scenario_name") + \
945+
pn.labs(y="Contribution to Energy Balance (TWh)") + \
946+
pn.scales.scale_color_manual(values=colors, aesthetics="color", na_value=colors["Other"]) + \
947+
pn.scales.scale_x_continuous(
948+
name="Month",
949+
labels=["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"],
950+
breaks=(15, 46, 76, 106, 137, 167, 198, 228, 259, 289, 319, 350),
951+
limits=(0, 366)) + \
952+
pn.scales.scale_linetype_manual(
953+
values={"Dispatch Limit": "dotted", "Dispatch": "solid"}
954+
) + \
955+
pn.theme(
956+
figure_size=(pn.options.figure_size[0] * tools.num_scenarios, pn.options.figure_size[1] * num_periods))
957+
958+
tools.save_figure(plot.draw())
832959

833960
@graph(
834961
"curtailment_per_period",

0 commit comments

Comments
 (0)