Reserve markets#
License |
yes |
Release version |
10.6.1 |
The multi-market functionality enables Prodrisk to account for reserve sales in the optimization and cut calculation. Two reserve markets are implemented, one for up regulation and one for down regulation. For each reserve market, one can specify the maximum market capacity, an obligation to be fulfilled, the reserve price, the maximum allocation per module, and penalty values. All of these are time-dependent. Market input can be given with custom resolution, but will be automatically converted to the same price periods as defined for the spot market.
Outputs are the reserve allocations per module per price period. Prodrisk also returns the reserve price as used in the optimization, i.e., with spot price resolution.
This is a licensed functionality that is made available on purchase.
Assumptions#
The reserve price is deterministic (same in each scenario).
Sales on all markets happen at the same time with spot price resolution.
There is no activation of reserves.
Input format#
To activate the use of reserve markets, set use_reserve_up and/or use_reserve_down to 1 in the API, or in the file prodrisk.cpar when running from the command line. In the API, reserve market inputs are given as time-series attributes on the area object: reserve_up_price, total_reserve_up_capacity, reserve_up_obligation, reserve_up_obligation_cost, and analogously for reserve_down_*.
When running from the command line, these inputs are contained in the new input file Reserve.csv, with corresponding attributes: Reserve_Up_Price, Reserve_Up_Market_Capacity, Reserve_Up_Obligation, Reserve_Up_Obligation_Penalty, and analogously for Reserve_Down_*. The file format is shown below:
If only one value is given for a week, the same value is used for all price periods. If a week is omitted, the previous week is repeated. Default values are constant zero for obligation and price, and 30% of CTANK for the obligation cost. The market capacity is mandatory input.
The inputs on the module level have weekly resolution, and are given through new input attributes max_reserve_up, max_reserve_up_cost, max_reserve_down, max_reserve_down_cost in the API. Default values are zero for the allocation limits, and 50% of CTANK for the associated costs.
When running from the command line, the new inputs are given in constraints.xml, with corresponding attributes: MaxCapUp, MaxCapUp_Penalty, MaxCapDown and MaxCapDown_Penalty. The file format is shown below:
Output format#
The allocation per module per price period for each reserve market is accessed in the API through the attributes reserve_up_allocation and reserve_down_allocation, or in detsimres.h5 under hydro_module_results as up_regulation and down_regulation. In the API there are additional area attributes total_reserve_up_allocation, total_reserve_down_allocation for the summed allocation of the whole system. The reserve prices with spot price resolution are written to the output attributes output_reserve_up_price, output_reserve_down_price on the area object, and to ENMRES.h5.
Calculation time#
The impact of calculation time mainly depends on (1) the number of modules participating in the reserve market and (2) the relation of market limits and module limits. In a system where the majority of plants can sell reserves, a loose market limit (larger than the summed allocatable capacity of all module) may lead to an increase of calculation time on the order of 20%. In contrast, a tight market limit (similar to or below the summed allocatable capacity) can increase calculation times drastically (doubling is possible). Note that the optimization is more complex in the latter case: The solver finds the optimal distribution of the market limit on the available modules. Thus, users who already know this distribution from experience can save calculation time by setting module limits that sum up to the intended market limit, while setting a loose bound on the market.
Example#
In this example, we show how to prepare input for a simple Prodrisk run with reserve markets, both through input files and in pyprodrisk. We consider a system with both up and down regulation, and the following dummy data:
Modules: ModuleA with id 101 and ModuleB with id 102
Price periods: \(5\) sequential periods with lengths \(42\),\(42\),\(42\),\(24\),\(18\)
Number of weeks: \(10\)
Market capacity:
Up regulation: \(100\) (weeks 1-6), \(120\) (weeks 7-10)
Down regulation: \(80\) (constant)
Market obligation:
Up regulation: \(20\) (constant)
Down regulation: \(0\) (constant)
Reserve price:
Equal for up and down regulation
Weeks 1-8:
Price period 1-3: \(12\)
Price period 4-5: \(8\)
Weeks 9-10:
Price period 1-3: \(15\)
Price period 4-5: \(10\)
ModuleA: Maximum allocation:
Up regulation: \(50\) (weeks 1-6), \(60\) (weeks 7-10)
Down regulation: \(50\) (constant)
ModuleB: Maximum allocation:
Up regulation: \(50\) (constant)
Down regulation: \(40\) (constant)
When running Prodrisk from the command line, we need to edit prodrisk.CPAR and constraints.xml, as well as creating the new file Reserve.csv.
prodrisk.CPAR#
In prodrisk.CPAR, we activate up and down regulation by setting
use_reserve_up 1,
use_reserve_down 1,
constraints.xml#
In constraints.xml, the module-specific reserve market input is written:
Reserve.csv#
The remaining input is written in the new file Reserve.csv:
PyProdrisk#
When using pyprodrisk, we activate up and down regulation through the setting attributes use_reserve_up and use_reserve_down. Reserve market input is written to a series of txy attributes:
prodrisk.use_reserve_up = 1
prodrisk.use_reserve_down = 1
area.total_reserve_up_capacity.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0, 6]],
data=[100.0, 120.0]))
area.reserve_up_obligation.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]],
data=[20.0]))
area.reserve_up_price.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0, 6]],
data=[100.0, 120.0]))
area.reserve_up_price.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=w) + pd.Timedelta(hours=i) for w in range(10) for i in [0,42,84,126,150]],
data=[12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
15.0, 15.0, 15.0, 10.0, 10.0,
15.0, 15.0, 15.0, 10.0, 10.0]))
area.total_reserve_down_capacity.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]],
data=[80.0]))
area.reserve_down_price.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=w) + pd.Timedelta(hours=i) for w in range(10) for i in [0,42,84,126,150]],
data=[12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
12.0, 12.0, 12.0, 8.0, 8.0,
15.0, 15.0, 15.0, 10.0, 10.0,
15.0, 15.0, 15.0, 10.0, 10.0]))
#ModuleA
modA.max_reserve_up.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0, 6]],
data=[50.0, 60.0]))
modA.max_reserve_down.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]],
data=[50.0]))
#ModuleB
modB.max_reserve_up.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]],
data=[50.0]))
modB.max_reserve_down.set(pd.Series(name=0.0,
index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]],
data=[40.0]))