Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extended GC plot: add price axis #228

Merged
merged 2 commits into from
Dec 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading