Discretization of droop#
The model setup for the iterative discretization of droop results presented below is available in the following formats:
pyshop
YAML
ASCII
Iterative droop discretization#
A simple system with 6 generators located on two separate plants will be used to illustrate the basic features of the droop discretization heuristic in SHOP. The droop variable is a continuous variable in SHOP, but there is usually a set of discrete droop values that can be implemented in the real world. The discrete droop functionality in SHOP enables the user to specify a set of discrete legal values that the droop variable in SHOP will be rounded and fixed to in the next iteration(s). This is a heuristic approach to the problem, since the droop variables are not formulated as discrete variables in the optimization problem. This would require the use of extra binary variables that will impact the tractability and calculation time of SHOP. Note that the droop discretization functionality requires the SHOP_DISCRETE_DROOP license. It is also possible to use the droop discretization functionality in combination with the SHOP_SEPARATE_DROOP license, which allows individual units to have separate droop settings for FCR-N, FCR-D-up, and FCR-D-down.
First, we create and run the basic model without any droop discretization:
#Necessary imports used in all examples
import pandas as pd
import plotly.graph_objs as go
from pyshop import ShopSession
#Functions used in this example for building and solving a simple model with cuts
from discrete_droop import build_model, run_model, get_gen_droop
#Create a standard ShopSession
shop=ShopSession()
#Build a simple model with two reservoirs, two plants, and 6 generators.
build_model(shop)
#Display topology to the screen
display(shop.model.build_connection_tree())
#Add FCR_N obligation in both directions where all generators can participate
fcr_group = shop.model.reserve_group.add_object("fcr_group")
fcr_group.fcr_n_up_obligation.set(20)
fcr_group.fcr_n_down_obligation.set(20)
for gen in shop.model.generator:
fcr_group.connect_to(gen)
#Run an optimization without any droop discretization
run_model(shop)
The resulting droop values can now be plotted. Note that the plotting below removes the time information form the droop values and plots them for the same x value:
#Display the resulting droop values from the optimization
#Retrieve the generator droop results
gen_droop = get_gen_droop(shop)
gen_names = [gen.get_name() for gen in shop.model.generator]
fig = go.Figure(layout={'title':"Droop values",'xaxis_title':"Generator",'yaxis_title':"Droop [%]"})
#Add dashed lines to show the integer values between 1 and 12
for i in range(1,13):
fig.add_trace(go.Scatter(showlegend=False,x=gen_names,y=[i]*len(gen_names),mode='lines', line={'color': "black", 'width': 0.5,'dash':"dash"}))
for gen_name, droop in gen_droop.items():
fig.add_trace(go.Scatter(showlegend=False,x=[gen_name]*len(droop),y=droop,mode='markers'))
fig.show()
The optimized droop values shown above are not necessarily legal or practical to implement in the real world. It is possible to round down and fix the droop values to the closest legal discrete value in SHOP. Each unit can have its own list of legal droop values specified by the double_array attribute discrete_droop_values. If no list is defined, the integers are assumed to be the legal values for the unit. The command set droop_discretization_limit
Note that new attributes such as fcr_d_down_discrete_droop_values are available when using the separate droop functionality in combination with the discrete droop functionality. Building of separate droop variables for FCR-N, FCR-D-up, and FCR-D-down is activated by setting the integer attribute separate_droop to 1 for the generator or pump.
Now we create an identical SHOP model but add specified legal discrete droop values for the generators in Plant2. By gradually increasing the droop_discretization_limit between the incremental iterations, the droop values are iteratively fixed to legal values.
#Create a standard ShopSession
shop=ShopSession()
#Build a simple model with two reservoirs, two plants, and 6 generators.
build_model(shop)
#Add FCR_N obligation in both directions where all generators can participate
fcr_group = shop.model.reserve_group.add_object("fcr_group")
fcr_group.fcr_n_up_obligation.set(20)
fcr_group.fcr_n_down_obligation.set(20)
for gen in shop.model.generator:
fcr_group.connect_to(gen)
#Set specific legal discrete droop values for generators in Plant2 (1,1.5,2,2.5,...,12)
plant2 = shop.model.plant.Plant2
for gen in plant2.generators:
gen.discrete_droop_values.set([1+0.5*i for i in range(23)])
#Run the full iterations and the first incremental iteration without any fixing and discretization
shop.start_sim([], ['3'])
shop.set_code(['incremental'], [])
shop.start_sim([], ['1'])
#Save the droop results before any fixing and discretization
gen_droop = get_gen_droop(shop)
droop_results = [gen_droop]
#Gradually fix droop values for each following iteration
for d in [3,6,9,12]:
shop.set_droop_discretization_limit([],[d])
shop.start_sim([], ['1'])
#Save the droop results for each generator after each iteration
gen_droop = get_gen_droop(shop)
droop_results.append(gen_droop)
Now we can plot the evolution of the droop results for each generator for the four final incremental iterations:
for desc,gen_droop in zip(["before fixing","fixed below 3","fixed below 6","fixed below 9","fixed below 12"], droop_results):
fig = go.Figure(layout={'title':f"Droop values {desc}",'xaxis_title':"Generator",'yaxis_title':"Droop [%]"})
for i in range(1,13):
fig.add_trace(go.Scatter(showlegend=False,x=gen_names,y=[i]*len(gen_names),mode='lines', line={'color': "black", 'width': 0.5,'dash':"dash"}))
for i in range(11):
fig.add_trace(go.Scatter(showlegend=False,x=["Plant2_G1","Plant2_G4"],y=[1.5+i,1.5+i],mode='lines', line={'color': "red", 'width': 0.5,'dash':"dash"}))
for gen_name,droop_values in gen_droop.items():
fig.add_trace(go.Scatter(showlegend=False,x=[gen_name]*len(droop_values),y=droop_values,mode='markers'))
fig.show()
Since the default upper bound for the droop is 12 in SHOP, all droop values are rounded and fixed when the discretization limit is set to 12 before the final iteration. The droop values of the generators in Plant1 are all fixed to integer values in the end, while the generators in Plant2 are allowed to take values defined earlier with the discrete_droop_values attribute.