Skip to content

Commit

Permalink
Merge pull request #228 from rl-institut/feature/ext_gc_plot_price
Browse files Browse the repository at this point in the history
extended GC plot: add price axis
  • Loading branch information
j-brendel authored Dec 5, 2024
2 parents f67e1c3 + 5e447a3 commit 866b8b3
Showing 1 changed file with 59 additions and 24 deletions.
83 changes: 59 additions & 24 deletions simba/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def generate_plots(schedule, scenario, args):
plot_consumption_per_rotation_distribution(extended_plots_path, schedule)
plot_distance_per_rotation_distribution(extended_plots_path, schedule)
plot_charge_type_distribution(extended_plots_path, scenario, schedule)
plot_gc_power_timeseries(extended_plots_path, scenario, schedule)
plot_gc_power_timeseries(extended_plots_path, scenario, schedule, args)
plot_active_rotations(extended_plots_path, scenario, schedule)

# revert logging override
Expand Down Expand Up @@ -557,7 +557,7 @@ def plot_charge_type_distribution(extended_plots_path, scenario, schedule):
plt.close()


def plot_gc_power_timeseries(extended_plots_path, scenario, schedule):
def plot_gc_power_timeseries(extended_plots_path, scenario, schedule, args):
"""Plots the different loads (total, feedin, external) of all grid connectors.
:param extended_plots_path: directory to save plot to
Expand All @@ -566,6 +566,8 @@ def plot_gc_power_timeseries(extended_plots_path, scenario, schedule):
:type scenario: spice_ev.Scenario
:param schedule: provides default time windows
:type schedule: simba.schedule.Schedule
:param args: Configuration arguments
:type args: argparse.Namespace
"""
for gcID, gc in scenario.components.grid_connectors.items():
fig, ax = plt.subplots()
Expand All @@ -579,8 +581,14 @@ def plot_gc_power_timeseries(extended_plots_path, scenario, schedule):
"battery power [kW]",
"bat. stored energy [kWh]",
]
# for stations simulated with balanced market, plot price as well
station = schedule.stations[gcID]
plot_price = vars(args).get(f"strategy_{station['type']}") == "balanced_market"
if plot_price:
headers.append("price [ct/kWh]")

has_battery_column = False
has_prices = False

# find time column
time_index = agg_ts["header"].index("time")
Expand All @@ -595,34 +603,61 @@ def plot_gc_power_timeseries(extended_plots_path, scenario, schedule):
# column does not exist
continue

if header == "bat. stored energy [kWh]":
if header == "price [ct/kWh]":
has_prices = True
twin_price = ax.twinx()
# get next color from color cycle (just plotting would start with first color)
next_color = plt.rcParams['axes.prop_cycle'].by_key()["color"][header_index]
twin_price.plot(
time_values, header_values, label=header, c=next_color, linestyle="dashdot")
twin_price.yaxis.label.set_color(next_color)
twin_price.tick_params(axis='y', colors=next_color)
twin_price.set_ylabel("price [ct/kWh]")
# add dummy values to primary axis for legend
ax.plot([], [], next_color, linestyle="dashdot", label=header)
elif header == "bat. stored energy [kWh]":
has_battery_column = True
# special plot for battery: same subplot, different y-axis
ax2 = ax.twinx()
ax2.set_ylabel("stored battery energy [kWh]")
twin_bat = ax.twinx()
# get next color from color cycle (just plotting would start with first color)
next_color = plt.rcParams['axes.prop_cycle'].by_key()["color"][header_index]
ax2.plot(
time_values, header_values,
label=header, c=next_color, linestyle="dashdot")
ax2.legend()
fig.set_size_inches(8, 4.8)
twin_bat.plot(
time_values, header_values, label=header, c=next_color, linestyle="dashdot")
twin_bat.yaxis.label.set_color(next_color)
twin_bat.tick_params(axis='y', colors=next_color)
twin_bat.set_ylabel("stored battery energy [kWh]")
# add dummy values to primary axis for legend
ax.plot([], [], next_color, linestyle="dashdot", label=header)
else:
# normal (non-battery) plot
# normal plot (no price or battery)
ax.plot(time_values, header_values, label=header)

if plot_price and not has_prices:
logging.error(f"Plot GC power: {gcID} simulated with balanced_market, but has no price")

# align y axis so that 0 is shared
# (limits not necessary, as power, energy and prices can't be compared directly)
# find smallest ratio of all axes
ax_ylims = ax.axes.get_ylim()
yratio = ax_ylims[0] / ax_ylims[1]
if has_prices:
price_ylims = twin_price.axes.get_ylim()
ax_yratio = ax_ylims[0] / ax_ylims[1]
yratio = min(ax_yratio, yratio)
if has_battery_column:
# align y axis so that 0 is shared
# (limits not necessary, as power and energy can't be compared directly)
ax1_ylims = ax.axes.get_ylim()
ax1_yratio = ax1_ylims[0] / ax1_ylims[1]
ax2_ylims = ax2.axes.get_ylim()
ax2_yratio = ax2_ylims[0] / ax2_ylims[1]
if ax1_yratio < ax2_yratio:
ax2.set_ylim(bottom=ax2_ylims[1]*ax1_yratio)
else:
ax.set_ylim(bottom=ax1_ylims[1]*ax2_yratio)
plt.tight_layout()
bat_ylims = twin_bat.axes.get_ylim()
ax_yratio = ax_ylims[0] / ax_ylims[1]
yratio = min(ax_yratio, yratio)

ax.set_ylim(bottom=ax_ylims[1]*yratio)
if has_prices:
twin_price.set_ylim(bottom=price_ylims[1]*yratio)
if has_battery_column:
twin_bat.set_ylim(bottom=bat_ylims[1]*yratio)
plt.tight_layout()

if has_prices and has_battery_column:
# offset battery yaxis, otherwise overlap with prices
twin_bat.spines.right.set_position(("axes", 1.2))

# show time windows
time_windows = agg_ts.get("window signal [-]")
Expand Down Expand Up @@ -659,7 +694,7 @@ def plot_gc_power_timeseries(extended_plots_path, scenario, schedule):
label=label, facecolor=color, alpha=0.2)
start_idx = i

ax.legend()
ax.legend() # fig.legend places legend outside of plot
# plt.xticks(rotation=30)
ax.set_ylabel("Power [kW]")
ax.set_title(f"Power: {gcID}")
Expand Down

0 comments on commit 866b8b3

Please sign in to comment.