--- jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.13.8 kernelspec: display_name: Python 3 (ipykernel) language: python name: python3 --- (battery_rolling_max_discharge)= # Rolling max energy discharge limit on battery This example demonstrates how the [battery](battery) [rolling_max_discharge_limit](battery:rolling_max_discharge_limit) works. The constraints impose a maximum total energy discharge within predefined time periods. The functionality can be used to e.g. enforce a maximum number of battery cycles within 24 hours. The model setup for this example is available here: - [battery_model.py](battery_model.py) ```{code-cell} ipython3 #Necessary imports import pandas as pd from pyshop import ShopSession import plotly.graph_objs as go from plotly.subplots import make_subplots import numpy as np from numpy.lib.stride_tricks import sliding_window_view #Functions used in this example for building a SHOP modeland running it from battery_model import build_model, run_model ``` ## Create SHOP session and import basic model This example model has one [battery](battery) that is connected to a [busbar](busbar). We will add rolling max discharge constraints that limit the battery energy discharge to maximum 2 MWh within each 2 hour time window. ```{code-cell} ipython3 #Create a standard ShopSession shop=ShopSession() #Build SHOP model build_model(shop) #Define variables starttime = shop.get_time_resolution()['starttime'] max_discharge = 2 time_window = 120 ``` ## Run model without rolling discharge constraints We start by running the model without any rolling max discharge constraints on the battery, and plot the results. The upper plot shows the battery [discharge](battery:power_discharge) for every time step within the optimization period. The lower plot shows the total energy discharge within each consecutive 2 hours, which is what the max rolling discharge limit will constrain. As the plot shows, without the rolling max discharge constraints, the total energy discharge is larger than the limit for all of the time intervals. ```{code-cell} ipython3 # Run model run_model(shop) # Get the battery discharge battery = shop.model.battery.Battery discharge = battery.power_discharge.get() #MW discharge_within_time_windows = sliding_window_view(discharge.values, int(120/15)).sum(axis=1)*15/60 #MWh #Plot battery discharge fig = make_subplots() fig.update_layout(title="Battery discharge") fig.update_yaxes(title_text="Discharge [MW]") fig.add_trace(go.Bar(x=discharge.index, y=discharge.values)) fig.show() #Plot total battery discharge within each time window fig = make_subplots() fig.update_layout(title="Total battery discharge within each time window") fig.update_yaxes(title_text="Total discharge [MWh]") fig.add_trace(go.Bar(x=np.arange(0,len(discharge_within_time_windows), 1), y=discharge_within_time_windows, name = "discharge")) fig.add_trace(go.Scatter(x=np.arange(0,len(discharge_within_time_windows), 1), y=np.zeros(len(discharge.values))+max_discharge, name = "max discharge limit")) fig.show() ``` # Add rolling max discharge constraints and rerun model We will now add the max rolling discharge constraint to our battery. We also add historical battery discharge data through the [historical_discharge](battery:historical_discharge) attribute, which affects the constraints for the first few time steps. We then rerun the model. The upper plot shows that the discharge is lower than before applying the constraints. For the first few time steps, there is no battery discharge, due to the historical discharge data. The lower plot shows that now, the total discharge is within the maximum limit for all 2 hour time windows. ```{code-cell} ipython3 #Set max rolling discharge limits and historical discharge on battery battery.rolling_max_discharge_limit.set([pd.Series([max_discharge], index=[time_window], name=starttime)]) battery.historical_discharge.set(pd.Series([2, 0.0], index=[starttime - pd.Timedelta(minutes=90), starttime - pd.Timedelta(minutes=30)])) # Run model run_model(shop) # Get the battery discharge battery = shop.model.battery.Battery discharge = battery.power_discharge.get() #MW historical_discharge = battery.historical_discharge.get().resample("15min").ffill() #MW total_discharge = historical_discharge.add(discharge, fill_value=0).astype(historical_discharge.dtype) #MW total_discharge_within_time_windows = sliding_window_view(total_discharge.values, int(120/15)).sum(axis=1)*15/60 #MWh #Plot battery discharge fig = make_subplots() fig.update_layout(barmode="stack", title="Battery discharge") fig.update_yaxes(title_text="Discharge [MW]") fig.add_trace(go.Bar(x=total_discharge.index, y=total_discharge.values*15/60, name="discharge")) fig.add_trace(go.Scatter(x=[starttime,starttime], y=[0,0.5],mode="lines", line = dict(color = 'grey', dash = 'dot'), name = "Optimization start")) fig.show() #Plot total battery discharge within each time window fig = make_subplots() fig.update_layout(barmode="stack", title="Total battery discharge within each time window") fig.update_yaxes(title_text="Total discharge [MWh]") fig.add_trace(go.Bar(x=np.arange(0,len(total_discharge_within_time_windows), 1), y=total_discharge_within_time_windows, name = "discharge")) fig.add_trace(go.Scatter(x=np.arange(0,len(total_discharge_within_time_windows), 1), y=np.zeros(len(total_discharge.values))+max_discharge, name = "max discharge limit")) fig.show() ```