Changing time resolution

Changing time resolution#

It is usually not possible to change the time resolution (start time, end time, time unit, and time step length) of the optimization after it has been specified in SHOP. However, this is now possible with the online SHOP functionality. This example will show how the time resolution can be changed between iterations in SHOP, and how SHOP handles the resampling of time series data.

The SHOP_ONLINE_OPTIMIZATION license in the “Online Optimization Capabilities” license group is required to change the time resolution after it has been set the first time in SHOP. The following YAML model will be used as a base for the rest of the example:

from pyshop import ShopSession
import pandas as pd
import plotly.graph_objects as go
pd.options.plotting.backend = "plotly"

The original time resolution has a horizon of three days with hourly time resolution:

shop = ShopSession()
shop.load_yaml("model.yaml")
time_res_orig = shop.get_time_resolution()

start_orig = time_res_orig["starttime"]
end_orig = time_res_orig["endtime"]
unit_orig = time_res_orig["timeunit"]
step_size_orig = time_res_orig["timeresolution"]

print(f"Start time: {start_orig}")
print(f"End time: {end_orig}")
print(f"Time unit: {unit_orig}")
print(f"Step sizes: {step_size_orig.values}")
Start time: 2018-01-23 00:00:00
End time: 2018-01-26 00:00:00
Time unit: hour
Step sizes: [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1.]

First, a single iteration with this hourly time resolution is performed, and the reservoir volume for Reservoir2 is retrieved:

shop.start_sim([],3)

rsv = shop.model.reservoir["Reservoir2"]
storage_orig = rsv.storage.get()

Now, the start and end times of the optimization are shifted 24 hours forward in time, and the step size is changed to be 15 minutes. Note that it is required to set the command activate online_optimization /on before changing the time resolution in an existing model:

start_new = start_orig + pd.Timedelta(hours = 24)
end_new = end_orig + pd.Timedelta(hours = 24)

shop.activate_online_optimization("on", [])
shop.set_time_resolution(start_new, end_new, "minute", pd.Series([15], index=[start_new]))

storage_resampled = rsv.storage.get()

After resetting the time resolution, all existing time series have automatically been resampled. The original and resampled reservoir trajectories are plotted in the figure below:

fig = go.Figure()
fig.add_trace(go.Scatter(x=[start_new, start_new], y=[min(storage_orig)*0.9, max(storage_orig)*1.1], name="new start time", line=dict(dash='dash')))
fig.add_trace(go.Scatter(x=[end_orig, end_orig], y=[min(storage_orig)*0.9, max(storage_orig)*1.1], name="original end time", line=dict(dash='dash')))

fig.add_trace(go.Scatter(x=storage_orig.index, y=storage_orig.values, name="Original volume"))
fig.add_trace(go.Scatter(x=storage_resampled.index, y=storage_resampled.values, name="Resampled volume", line=dict(dash='dash')))

fig.update_layout(xaxis_title="Time", yaxis_title="Volume [Mm3]",title="Reservoir volume before and after changing time resolution")
fig.show()

Note that SHOP keeps the data before the new start time of the optimization as historical values for the time series. These values are not resampled to 15 minute intervals like the other values inside the new optimization horizon. Since SHOP does not know what the reservoir volume (or any other time series) are supposed to be in the interval between the original and new end times, the last value of the time series is forward filled into the unknown time steps. It is important that the user sets all input time series again to have proper data for the whole period, otherwise the price, inflow, etc. will be constant. Similarly, the time series will be constant for all new time steps inside the old hourly time steps, and so input data with a 15 minute resolution (such as the spot price) should also be set again.

To simplify this example, we will reset the time resolution again so that the new and original end times are identical:

end_new = end_orig
shop.set_time_resolution(start_new, end_new, "minute", pd.Series([15], index=[start_new]))

Now we run a new iteration with the updated time resolution. We also switch to incremental iterations to lock in the hourly unit commitment status calculated with the faster hourly model. This means that the units are on or off for whole clock hours instead of potentially switching on or off every 15 minutes. This also helps with calculation time since running the full iterations with hourly time resolution is faster than the more granular 15 minute time resolution:

shop.set_code("incremental", [])
shop.start_sim([],1)

The reservoir storage results compared to the original hourly run is shown below:

storage_final = rsv.storage.get()
fig = go.Figure()
fig.add_trace(go.Scatter(x=[start_new, start_new], y=[min(storage_orig)*0.9, max(storage_orig)*1.1], name="New start time", line=dict(dash='dash')))

fig.add_trace(go.Scatter(x=storage_orig.index, y=storage_orig.values, name="Hourly run"))
fig.add_trace(go.Scatter(x=storage_final.index, y=storage_final.values, name="15 min run", line=dict(dash='dash')))

fig.update_layout(xaxis_title="Time", yaxis_title="Volume [Mm3]",title="Reservoir volume in the hourly and 15 min model")
fig.show()

We also see that the unit commitment status of the generator Plant1_G1 is constant for every clock hour since the commitment was decided based on the hourly model. Also note the hourly historical time series that is returned between the old and new start times:

gen = shop.model.generator["Plant1_G1"]
committed = gen.committed_out.get()

fig = go.Figure()
fig.add_trace(go.Scatter(x=[start_new, start_new], y=[-0.1, 1.1], name="New start time", line=dict(dash='dash')))
fig.add_trace(go.Scatter(x=committed.index, y=committed.values, name="Committed status", mode="lines+markers"))
fig.update_layout(xaxis_title="Time", yaxis_title="On/off status",title=f"Commitment status of generator {gen.get_name()}")
fig.show()

SHOP does not save the entire raw input time series that was set by the user, so changing the step size of the time resolution back and forth will result in a loss of precision. This is due to resampling an already resampled time series, but is mostly a problem when changing to a coarser time step size. The resampling also does not consider whether the data should be averaged, linearly interpolated, etc., the value at the start of the new time step is simply the value of the old time series at that point in time.