Commit 20214e54 authored by Niina Helistö's avatar Niina Helistö
Browse files

Merge branch 'dev' into LossOfLargestInterconnector

parents 4ec02a9b 39a6bfc3
......@@ -26,12 +26,11 @@ Created by:
Ran Li
Ciara O'Dwyer
- Based on Stochastic Model Predictive Control method [1].
- Enables multiple different models (m) to be implemented by changing
the temporal structure of the model. (MULTI-MODEL RUNS TO BE IMPLEMENTED)
This is a GAMS implementation of the Backbone energy system modelling framework
[1]. Features include:
- Based on Stochastic Model Predictive Control method [2].
- Time steps (t) can vary in length.
- Short term forecast stochasticity (f) and longer term statistical uncertainty (s).
- Can handle ramp based dispatch in addition to energy blocks. (TO BE IMPLEMENTED)
GAMS command line arguments
......@@ -63,7 +62,10 @@ GAMS command line arguments
References
----------
[1] K. Nolde, M. Uhr, and M. Morari, Medium term scheduling of a hydro-thermal
[1] N. Helist et al., Backbone---An Adaptable Energy Systems Modelling Framework,
Energies, vol. 12, no. 17, p. 3388, Sep. 2019. Available at:
https://dx.doi.org/10.3390/en12173388.
[2] K. Nolde, M. Uhr, and M. Morari, Medium term scheduling of a hydro-thermal
system using stochastic model predictive control, Automatica, vol. 44,
no. 6, pp. 15851594, Jun. 2008.
......@@ -126,7 +128,7 @@ $include '%input_dir%/modelsInit.gms'
* === Simulation ==============================================================
$include 'inc/3a_periodicInit.gms' // Initialize modelling loop
loop(modelSolves(mSolve, tSolve),
loop(modelSolves(mSolve, tSolve)$(execError = 0),
solveCount = solveCount + 1;
$$include 'inc/3b_periodicLoop.gms' // Update modelling loop
$$include 'inc/3c_inputsLoop.gms' // Read input data that is updated within the loop
......@@ -143,7 +145,6 @@ $ifthene.debug %debug%>1
$$include defOutput/debugSymbols.inc
;
$endif.debug
if(execError, put log "!!! Errors encountered: " execError:0:0);
);
$if exist '%input_dir%/3z_modelsClose.gms' $include '%input_dir%/3z_modelsClose.gms';
......@@ -164,6 +165,8 @@ execute_unload '%output_dir%/results.gdx',
$ife %debug%>0
execute_unload '%output_dir%/debug.gdx';
if(errorcount > 0, abort errorcount);
if(execError,
putclose log "!!! Errors encountered: " execError:0:0/;
abort "FAILED";
);
* === THE END =================================================================
......@@ -7,12 +7,15 @@ All notable changes to this project will be documented in this file.
- All input files, including *inputData.gdx*, are optional
- Enabling different combinations of LP and MIP online and invest variables
- Separate availability parameter for output units in the capacity margin constraint
- Parameter `gn_forecasts(*, node, timeseries)` to tell which nodes and timeseries use forecasts
### Changed
- Updated tool defintions for Sceleton Titan and Spine Toolbox
- The program will now stop looping in case of execution errors.
### Fixed
- Removed hard-coded `elec grids` from *setVariableLimits* and *rampSched files*
- Time series smooting not working at all (#100)
## [1.1] - 2019-04-17
### Added
......
# Backbone
Backbone is a generic energy network optimization tool written in [GAMS](https://www.gams.com/). It has been designed to be highly adaptable in different dimensions: temporal, spatial, technology representation and market design. The model can represent stochastics with a model predictive control method [1], with short-term forecasts and longer-term statistical uncertainties. Backbone can support multiple different models due to the modifiable temporal structure and varying lengths of the time steps.
Backbone is a generic energy network optimization tool written in [GAMS](https://www.gams.com/). It has been designed to be highly adaptable in different dimensions: temporal, spatial, technology representation and market design. The model can represent stochastics with a [model predictive control method](https://doi.org/10.1016/j.automatica.2008.03.002), with short-term forecasts and longer-term statistical uncertainties. Backbone can support multiple different models due to the modifiable temporal structure and varying lengths of the time steps.
[1] Nolde, K., Uhr, M., & Morari, M. (2008). Medium term scheduling of a hydro-thermal system using stochastic model predictive control. Automatica, 1585-1594.
If you use Backbone in a published work, please cite the [following publication](https://doi.org/10.3390/en12173388), which describes the Backbone energy systems modelling framework.
## Getting Started
......@@ -10,7 +10,9 @@ Make sure that you have [Git](https://git-scm.com/) version control system and a
In order to get a copy of the Backbone project, you need to clone it using Git. Copy and paste the URL of the original Backbone repository and select the directory where you want Backbone to be cloned. The URL of the original Backbone repository is https://gitlab.vtt.fi/backbone/backbone.
You should now have *Backbone.gms*, a few additional files and three subdirectories in the directory where you cloned Backbone. Note that you need to manually create two additional subdirectories in order to get Backbone working. These subdirectories should be named *input* and *output* and they should be created in the same directory where *Backbone.gms* is.
You should now have *Backbone.gms*, a few additional files and five subdirectories in the directory where you cloned Backbone.
Small example input datasets are provided online in the [wiki](https://gitlab.vtt.fi/backbone/backbone/wikis/Example-data-sets).
## Model File Structure
......@@ -29,8 +31,8 @@ Backbone has been designed with a modular structure, making it easier to change
* 2d_constraints.gms - Contains definitions for constraint equations.
* *Model Definition Files* - Contains GAMS definitions for different models, essentially lists the equations (constraints) that apply. Current files include *schedule.gms*, *building.gms* and *invest.gms*.
* 3a_periodicInit.gms - Initializes various data and sets for the solve loop.
* 3b_inputsLoop.gms - Instructions for possible data import inside the solve loop, as well as forecast in-the-loop improvements.
* 3c_periodicLoop.gms - Contains instructions for the forecast-time structure of the desired model.
* 3b_periodicLoop.gms - Contains instructions for the forecast-interval structure of the desired model.
* 3c_inputsLoop.gms - Contains instructions for updating the forecast data, optional forecast improvements, aggregating time series data for the time intervals, and other input data processing.
* 3d_setVariableLimits.gms - Defines the variable boundaries for each solve.
* 3e_solve.gms - Contains the GAMS solve command for using the solver.
* 3f_afterSolve.gms - Fixes some variable values after solve.
......@@ -45,7 +47,7 @@ Most of these files are under *\inc* in the Backbone folder, except for the mode
* timeAndSamples.inc - Contains definitions for the time, forecast and sample index ranges.
* modelsInit.gms - Contains model parameters for the solve (or a link to a template under *\defModels* to be used). Useful for any additional GAMS scripting.
Backbone folder contains three template files *1_options_temp.gms*, *timeAndSamples_temp.inc*, and *modelsInit_temp.gms* to provide examples of the input format. These files can be copied into *\input* and renamed to *1_options.gms*, *timeAndSamples.inc*, and *modelsInit.gms*.
Backbone folder contains template files *1_options_temp.gms*, *timeAndSamples_temp.inc*, and *modelsInit_temp.gms* to provide examples of the input format. These files can be copied into *\input* and renamed to *1_options.gms*, *timeAndSamples.inc*, and *modelsInit.gms*.
## When Simply Using Backbone
......@@ -62,6 +64,9 @@ When starting to use Backbone, there is no immediate need to understand every si
* Erkka Rinne
* Topi Rasku
* Niina Helisto
* Dana Kirchem
* Ran Li
* Ciara O'Dwyer
## License
......
......@@ -27,8 +27,11 @@ Model building /
// Unit Operation
q_maxDownward
* q_maxDownwardOfflineReserve
q_maxUpward
* q_maxUpwardOfflineReserve
* q_reserveProvision
* q_reserveProvisionOnline
* q_startup
* q_startuptype
* q_onlineLimit
......
......@@ -54,6 +54,8 @@ if (mType('building'),
// Define the probability (weight) of samples
p_msProbability('building', s) = 0;
p_msProbability('building', 's000') = 1;
p_msWeight('building', s) = 0;
p_msWeight('building', 's000') = 1;
* --- Define Time Step Intervals ----------------------------------------------
......
......@@ -27,8 +27,11 @@ Model invest /
// Unit Operation
q_maxDownward
q_maxDownwardOfflineReserve
q_maxUpward
q_maxUpwardOfflineReserve
q_reserveProvision
q_reserveProvisionOnline
q_startshut
q_startuptype
q_onlineOnStartUp
......
......@@ -58,9 +58,13 @@ if (mType('invest'),
// Define the probability (weight) of samples
p_msProbability('invest', s) = 0;
p_msProbability('invest', 's000') = 8760/504;
p_msProbability('invest', 's001') = 8760/504;
p_msProbability('invest', 's002') = 8760/504;
p_msProbability('invest', 's000') = 1;
p_msProbability('invest', 's001') = 1;
p_msProbability('invest', 's002') = 1;
p_msWeight('invest', s) = 0;
p_msWeight('invest', 's000') = 8760/504;
p_msWeight('invest', 's001') = 8760/504;
p_msWeight('invest', 's002') = 8760/504;
* --- Define Time Step Intervals ----------------------------------------------
......@@ -78,6 +82,10 @@ if (mType('invest'),
// Define the number of forecasts used by the model
mSettings('invest', 'forecasts') = 0;
// Define which nodes and timeseries use forecasts
//Option clear = gn_forecasts; // By default includes everything, so clear first
//gn_forecasts('wind', 'XXX', 'ts_cf') = yes;
// Define forecast properties and features
mSettings('invest', 't_forecastStart') = 0; // At which time step the first forecast is available ( 1 = t000001 )
mSettings('invest', 't_forecastLengthUnchanging') = 0; // Length of forecasts in time steps - this does not decrease when the solve moves forward (requires forecast data that is longer than the horizon at first)
......
......@@ -31,8 +31,11 @@ Model schedule /
q_resDemandLargestInfeedTransfer
// Unit Operation
q_maxDownward
q_maxDownwardOfflineReserve
q_maxUpward
q_maxUpwardOfflineReserve
* q_reserveProvision
q_reserveProvisionOnline
q_startshut
q_startuptype
q_onlineLimit
......
......@@ -64,6 +64,8 @@ if (mType('schedule'),
// Define the probability (weight) of samples
p_msProbability('schedule', s) = 0;
p_msProbability('schedule', 's000') = 1;
p_msWeight('schedule', s) = 0;
p_msWeight('schedule', 's000') = 1;
// If using long-term samples, uncomment
//ms_central('schedule', 's001') = yes;
......@@ -104,6 +106,10 @@ if (mType('schedule'),
// Define the number of forecasts used by the model
mSettings('schedule', 'forecasts') = 3;
// Define which nodes and timeseries use forecasts
//Option clear = gn_forecasts; // By default includes everything, so clear first
//gn_forecasts('wind', 'XXX', 'ts_cf') = yes;
// Define forecast properties and features
mSettings('schedule', 't_forecastStart') = 1; // At which time step the first forecast is available ( 1 = t000001 )
mSettings('schedule', 't_forecastLengthUnchanging') = 36; // Length of forecasts in time steps - this does not decrease when the solve moves forward (requires forecast data that is longer than the horizon at first)
......
......@@ -102,7 +102,11 @@ v_help_inc
// Unit Operation
q_maxDownward
q_maxDownwardOfflineReserve
q_maxUpward
q_maxUpwardOfflineReserve
q_reserveProvision
q_reserveProvisionOnline
q_startshut
q_startuptype
q_onlineLimit
......@@ -157,3 +161,4 @@ vq_gen
vq_resDemand
vq_resMissing
v_stateSlack
vq_capacity
......@@ -367,6 +367,7 @@ param_policy "Set of possible data parameters for grid, node, regulation" /
reserveReliability "Reliability parameter of reserve provisions"
reserve_increase_ratio "Unit output is multiplied by this factor to get the increase in reserve demand"
portion_of_infeed_to_reserve "Proportion of the generation of a tripping unit that needs to be covered by reserves from other units"
offlineReserveCapability "Proportion of an offline unit which can contribute to a category of reserve"
ReserveShareMax "Maximum reserve share of a group of units"
LossOfTrans
/
......
......@@ -83,7 +83,10 @@ Sets
restypeDirectionGroup(restype, up_down, group)
restypeDirectionGridNodeGroup(restype, up_down, grid, node, group)
gnuRescapable(restype, up_down, grid, node, unit) "Units capable and available to provide particular reserves"
gnuOfflineRescapable(restype, grid, node, unit) "Units capable and available to provide offline reserves"
restypeReleasedForRealization(restype) "Reserve types that are released for the realized time intervals"
offlineRes (restype) "Reserve types where offline reserve provision possible"
offlineResUnit (unit) "Units where offline reserve provision possible"
* --- Sets to define time, forecasts and samples ------------------------------
$$include '%input_dir%/timeAndSamples.inc'
......@@ -131,7 +134,8 @@ Sets
s_prev(s) "Temporary set for previous sample"
$if defined scenario
s_scenario(s, scenario) "Which samples belong to which scenarios"
gn_scenarios(*, node, *) "Which grid/flow, node and timeseries/param have data for long-term scenarios"
gn_forecasts(*, node, timeseries) "Which grid/flow, node and timeseries use short-term forecasts"
gn_scenarios(*, node, timeseries) "Which grid/flow, node and timeseries have data for long-term scenarios"
* --- Sets used for the changing unit aggregation and efficiency approximations
uft(unit, f, t) "Active units on intervals, enables aggregation of units for later intervals"
......
......@@ -17,7 +17,6 @@ $offtext
* --- Internal counters -------------------------------------------------------
Scalars
errorcount /0/
solveCount /0/
tSolveFirst "counter (ord) for the first t in the solve"
tSolveLast "counter for the last t in the solve"
......@@ -95,7 +94,7 @@ Parameters
* --- Probability -------------------------------------------------------------
Parameters
p_msWeight(mType, s) "Weight of sample"
p_msWeight(mType, s) "Temporal weight of sample: number of similar periods represented by sample s"
p_msProbability(mType, s) "Probability to reach sample conditioned on anchestor samples"
p_mfProbability(mType, f) "Probability of forecast"
p_msft_probability(mType, s, f, t) "Probability of forecast"
......@@ -130,6 +129,7 @@ Parameters
df_reserves(grid, node, restype, f, t) "Forecast index displacement needed to reach the realized forecast when committing reserves"
df_reservesGroup(group, restype, f, t) "Forecast index displacement needed to reach the realized forecast when committing reserves"
df_scenario(f, t) "Forecast index displacement needed to get central forecast data for long-term scenarios"
df_realization(f, t) "Displacement needed to reach the realized forecast on the current time step when no forecast is available"
// Sample displacement arrays
ds(s, t) "Displacement needed to reach the sample of previous time step"
......@@ -190,9 +190,6 @@ Parameters
p_tsMaxValue(*, node, timeseries) "Maximum allowed value of timeseries in grid/flow and node"
;
* Reset values for some parameters
Options clear = ts_influx_std, clear = ts_cf_std;
* --- Other time dependent parameters -----------------------------------------
Parameters
p_storageValue(grid, node, t) "Value of stored something at the end of a time step"
......
......@@ -414,6 +414,24 @@ gnuRescapable(restypeDirection(restype, up_down), gnu(grid, node, unit))
}
= yes;
// Units with offline reserve provision capabilities
gnuOfflineRescapable(restype, gnu(grid, node, unit))
$ { p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability')
}
= yes;
// Restypes with offline reserve provision possibility
offlineRes(restype)
$ {sum(gnu(grid, node, unit), p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability'))
}
= yes;
// Units with offline reserve provision possibility
offlineResUnit(unit)
$ {sum((gn(grid, node), restype), p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability'))
}
= yes;
// Node-node connections with reserve transfer capabilities
restypeDirectionGridNodeNode(restypeDirection(restype, up_down), gn2n(grid, node, node_))
$ { p_gnnReserves(grid, node, node_, restype, up_down)
......@@ -450,6 +468,7 @@ p_gnuReserves(gnu(grid, node, unit), restype, 'reserveReliability')
= 1;
// Reserve provision overlap decreases the capacity of the overlapping category
loop(restype,
p_gnuReserves(gnu(grid, node, unit), restype, up_down)
${ gnuRescapable(restype, up_down, grid, node, unit) }
= p_gnuReserves(grid, node, unit, restype, up_down)
......@@ -457,6 +476,7 @@ p_gnuReserves(gnu(grid, node, unit), restype, up_down)
+ p_gnuReserves(grid, node, unit, restype_, up_down)
* p_gnuRes2Res(grid, node, unit, restype_, up_down, restype)
); // END sum(restype_)
);
* =============================================================================
* --- Data Integrity Checks ---------------------------------------------------
......@@ -539,7 +559,7 @@ loop( unit,
); // END loop(effLevelGroupUnit)
);
* --- Check the start-up fuel fraction related data ---------------------------
* --- Check fuel fraction related data ----------------------------------------
loop( unit_fuel(unit)${sum(fuel, uFuel(unit_fuel, 'startup', fuel))},
if(sum(fuel, p_uFuel(unit, 'startup', fuel, 'fixedFuelFraction')) <> 1,
......@@ -549,6 +569,14 @@ loop( unit_fuel(unit)${sum(fuel, uFuel(unit_fuel, 'startup', fuel))},
);
);
loop( unit_fuel(unit)${sum(fuel, p_uFuel(unit, 'main', fuel, 'maxFuelFraction'))},
if(sum(uFuel(unit, 'main', fuel), 1) < 2,
put log '!!! Error occurred on unit ' unit.tl:0 /;
put log '!!! Abort: maxFuelFraction cannot be applied to units with only a single main fuel!' /;
abort "'maxFuelFraction' cannot be applied to units with only a single main fuel!"
);
);
* --- Check the shutdown time related data ------------------------------------
loop( unitStarttype(unit, starttypeConstrained),
......@@ -598,10 +626,14 @@ loop( (gnu(grid, node, unit), restypeDirection(restype, up_down)),
* --- Default values ---------------------------------------------------------
* =============================================================================
loop(timeseries$(not sameas(timeseries, 'ts_cf')),
p_autocorrelation(gn, timeseries) = 0;
p_tsMinValue(gn, timeseries) = -Inf;
p_tsMaxValue(gn, timeseries) = Inf;
);
p_autocorrelation(flowNode, 'ts_cf') = 0;
p_tsMinValue(flowNode, 'ts_cf') = 0;
p_tsMaxValue(flowNode, 'ts_cf') = 1;
* By default all nodes use forecasts for all timeseries
gn_forecasts(gn, timeseries) = yes;
gn_forecasts(flowNode, timeseries) = yes;
gn_forecasts(restype, node, 'ts_reserveDemand') = yes;
......@@ -61,9 +61,12 @@ equations
q_resDemandLargestInfeedTransferDown(restype, up_down, group, grid, node, node, s, f, t) "N-1 down reserve for transmission lines"
q_resDemandLargestInfeedTransfer(restype, up_down, group, grid, node, node, s, f, t) "N-1 up/down reserve for transmission lines"
// Unit Operation
q_maxDownward(grid, node, unit, mType, s, f, t) "Downward commitments will not undercut power plant minimum load constraints or maximum elec. consumption"
q_maxUpward(grid, node, unit, mType, s, f, t) "Upward commitments will not exceed maximum available capacity or consumed power"
q_reserveProvision(restype, up_down, grid, node, unit, s, f, t) "Reserve provision limited for units"
q_maxDownward(grid, node, unit, mType, s, f, t) "Downward commitments (v_gen and online v_reserve) will not undercut minimum (online) production capacity (+) or maximum (online) consumption capacity (-)"
q_maxDownwardOfflineReserve(grid, node, unit, mType, s, f, t) "Downward commitments (v_gen and all v_reserve) will not undercut zero production (+) or maximum consumption capacity (-)"
q_maxUpward(grid, node, unit, mType, s, f, t) "Upward commitments (v_gen and online v_reserve) will not exceed maximum (online) production capacity (+) or minimum (online) consumption capacity (-)"
q_maxUpwardOfflineReserve(grid, node, unit, mType, s, f, t) "Upward commitments (v_gen and all v_reserve) will not exceed maximum production capacity (+) or zero consumption (-)"
q_reserveProvision(restype, up_down, grid, node, unit, s, f, t) "Reserve provision limited for units with investment possibility"
q_reserveProvisionOnline(restype, up_down, grid, node, unit, s, f, t) "Reserve provision limited for units that are not capable of providing offline reserve"
q_startshut(mType, s, unit, f, t) "Online capacity now minus online capacity in the previous interval is equal to started up minus shut down capacity"
q_startuptype(mType, s, starttype, unit, f, t) "Startup type depends on the time the unit has been non-operational"
q_onlineOnStartUp(s, unit, f, t) "Unit must be online after starting up"
......
......@@ -656,7 +656,9 @@ q_maxDownward(gnu(grid, node, unit), msft(m, s, f, t))
) // END sum(gngnu_constrainedOutputRatio)
// Downward reserve participation
- sum(gnuRescapable(restype, 'down', grid, node, unit)${ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')},
- sum(gnuRescapable(restype, 'down', grid, node, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')
and not gnuOfflineRescapable(restype, grid, node, unit)
},
+ v_reserve(restype, 'down', grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t) // (v_reserve can be used only if the unit is capable of providing a particular reserve)
) // END sum(nuRescapable)
......@@ -735,7 +737,65 @@ q_maxDownward(gnu(grid, node, unit), msft(m, s, f, t))
] // END * p_unit(availability)
;
* --- Maximum Upwards Capacity ------------------------------------------------
* --- Maximum Downward Capacity for Production/Consumption, Online Reserves and Offline Reserves ---
q_maxDownwardOfflineReserve(gnu(grid, node, unit), msft(m, s, f, t))
${ gnuft(grid, node, unit, f, t)
and {
[ ord(t) < tSolveFirst + smax(restype, p_gnReserves(grid, node, restype, 'reserve_length')) // Unit is providing
and sum(restype, gnuRescapable(restype, 'down', grid, node, unit)) // downward reserves
]
}
and { sum(restype, gnuOfflineRescapable(restype, grid, node, unit))} // and it can provide some reserve products although being offline
}..
// Energy generation/consumption
+ v_gen(grid, node, unit, s, f, t)
// Considering output constraints (e.g. cV line)
+ sum(gngnu_constrainedOutputRatio(grid, node, grid_output, node_, unit),
+ p_gnu(grid_output, node_, unit, 'cV')
* v_gen(grid_output, node_, unit, s, f, t)
) // END sum(gngnu_constrainedOutputRatio)
// Downward reserve participation
- sum(gnuRescapable(restype, 'down', grid, node, unit)${ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')},
+ v_reserve(restype, 'down', grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t)
) // END sum(nuRescapable)
=G= // Must be greater than maximum consumption
// Consuming units
// Available capacity restrictions
- p_unit(unit, 'availability') // Consumption units are also restricted by their (available) capacity
* [
// Capacity factors for flow units
+ sum(flowUnit(flow, unit),
+ ts_cf_(flow, node, f, t, s)
) // END sum(flow)
+ 1${not unit_flow(unit)}
] // END * p_unit(availability)
* [
// Existing capacity
+ p_gnu(grid, node, unit, 'maxCons')
// Investments to new capacity
+ [
+ p_gnu(grid, node, unit, 'unitSizeCons')
]
* [
+ sum(t_invest(t_)${ ord(t_)<=ord(t)
},
+ v_invest_LP(unit, t_)${unit_investLP(unit)}
+ v_invest_MIP(unit, t_)${unit_investMIP(unit)}
) // END sum(t_invest)
] // END * p_gnu(unitSizeCons)
] // END * p_unit(availability)
;
* --- Maximum Upwards Capacity for Production/Consumption and Online Reserves ---
q_maxUpward(gnu(grid, node, unit), msft(m, s, f, t))
${ gnuft(grid, node, unit, f, t)
......@@ -754,7 +814,9 @@ q_maxUpward(gnu(grid, node, unit), msft(m, s, f, t))
gnu_output(grid, node, unit) // generators with investment possibility
and (unit_investLP(unit) or unit_investMIP(unit))
]
}}..
}
}..
// Energy generation/consumption
+ v_gen(grid, node, unit, s, f, t)
......@@ -766,7 +828,9 @@ q_maxUpward(gnu(grid, node, unit), msft(m, s, f, t))
) // END sum(gngnu_constrainedOutputRatio)
// Upwards reserve participation
+ sum(gnuRescapable(restype, 'up', grid, node, unit)${ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')},
+ sum(gnuRescapable(restype, 'up', grid, node, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')
and not gnuOfflineRescapable(restype, grid, node, unit)
},
+ v_reserve(restype, 'up', grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t)
) // END sum(nuRescapable)
......@@ -839,6 +903,63 @@ q_maxUpward(gnu(grid, node, unit), msft(m, s, f, t))
) // END sum(shutdownCounter)
;
* --- Maximum Upwards Capacity for Production/Consumption, Online Reserves and Offline Reserves ---
q_maxUpwardOfflineReserve(gnu(grid, node, unit), msft(m, s, f, t))
${ gnuft(grid, node, unit, f, t)
and {
[ ord(t) < tSolveFirst + smax(restype, p_gnReserves(grid, node, restype, 'reserve_length')) // Unit is providing
and sum(restype, gnuRescapable(restype, 'up', grid, node, unit)) // upward reserves
]
}
and { sum(restype, gnuOfflineRescapable(restype, grid, node, unit))} // and it can provide some reserve products although being offline
}..
// Energy generation/consumption
+ v_gen(grid, node, unit, s, f, t)
// Considering output constraints (e.g. cV line)
+ sum(gngnu_constrainedOutputRatio(grid, node, grid_output, node_, unit),
+ p_gnu(grid_output, node_, unit, 'cV')
* v_gen(grid_output, node_, unit, s, f, t)
) // END sum(gngnu_constrainedOutputRatio)
// Upwards reserve participation
+ sum(gnuRescapable(restype, 'up', grid, node, unit)${ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')},
+ v_reserve(restype, 'up', grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t)
) // END sum(nuRescapable)
=L= // must be less than available capacity
// Generation units
// Available capacity restrictions
+ p_unit(unit, 'availability') // Generation units are restricted by their (available) capacity
* [
// Capacity factor for flow units
+ sum(flowUnit(flow, unit),
+ ts_cf_(flow, node, f, t, s)
) // END sum(flow)
+ 1${not unit_flow(unit)}
] // END * p_unit(availability)
* [
// Capacity restriction
+ p_gnu(grid, node, unit, 'unitSizeGen')
* [
// Existing capacity
+ p_unit(unit, 'unitCount')
// Investments to new capacity
+ sum(t_invest(t_)${ ord(t_)<=ord(t)
},
+ v_invest_LP(unit, t_)${unit_investLP(unit)}
+ v_invest_MIP(unit, t_)${unit_investMIP(unit)}
) // END sum(t_invest)
] // END * p_gnu(unitSizeGen)
] // END * p_unit(availability)
;
* --- Reserve Provision of Units with Investments -----------------------------
q_reserveProvision(gnuRescapable(restypeDirectionGridNode(restype, up_down, grid, node), unit), sft(s, f, t))
......@@ -873,6 +994,39 @@ q_reserveProvision(gnuRescapable(restypeDirectionGridNode(restype, up_down, grid
] // How to consider reserveReliability in the case of investments when we typically only have "realized" time steps?
;
* --- Online Reserve Provision of Units with Online Variables -----------------
q_reserveProvisionOnline(gnuRescapable(restypeDirectionGridNode(restype, up_down, grid, node), unit), sft(s, f, t))
${ ord(t) <= tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')
and gnuft(grid, node, unit, f, t)
and not sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
ft_reservesFixed(group, restype, f+df_reservesGroup(group, restype, f, t), t))
and uft_online(unit, f ,t)
and not gnuOfflineRescapable(restype, grid, node, unit)
}..
+ v_reserve(restype, up_down, grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t)
=L=
+ p_gnuReserves(grid, node, unit, restype, up_down)
* p_gnu(grid, node, unit, 'unitSizeTot')
* [
+ v_online_LP(unit, s, f+df_central(f,t), t)${uft_onlineLP(unit, f ,t)}
+ v_online_MIP(unit, s, f+df_central(f,t), t)${uft_onlineMIP(unit, f, t)}
]
* p_unit(unit, 'availability') // Taking into account availability...
* [
// ... and capacity factor for flow units
+ sum(flowUnit(flow, unit),
+ ts_cf_(flow, node, f, t, s)
) // END sum(flow)
+ 1${not unit_flow(unit)}
] // How to consider reserveReliability in the case of investments when we typically only have "realized" time steps?
;
* --- Unit Startup and Shutdown -----------------------------------------------
q_startshut(ms(m, s), uft_online(unit, f, t))
......@@ -1156,7 +1310,9 @@ q_rampUpLimit(ms(m, s), gnuft_ramp(grid, node, unit, f, t))
// Ramp speed of the unit?
+ v_genRamp(grid, node, unit, s, f, t)
+ sum(gnuRescapable(restype, 'up', grid, node, unit)${ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')},
+ sum(gnuRescapable(restype, 'up', grid, node, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')
and not gnuOfflineRescapable(restype, grid, node, unit)
},
+ v_reserve(restype, 'up', grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t) // (v_reserve can be used only if the unit is capable of providing a particular reserve)
) // END sum(nuRescapable)
/ p_stepLength(m, f, t)
......@@ -1274,6 +1430,7 @@ q_rampUpLimit(ms(m, s), gnuft_ramp(grid, node, unit, f, t))
) // END * v_shutdown
;
* --- Ramp Down Limits --------------------------------------------------------
q_rampDownLimit(ms(m, s), gnuft_ramp(grid, node, unit, f, t))
......@@ -1289,7 +1446,9 @@ q_rampDownLimit(ms(m, s), gnuft_ramp(grid, node, unit, f, t))
// Ramp speed of the unit?
+ v_genRamp(grid, node, unit, s, f, t)
- sum(gnuRescapable(restype, 'down', grid, node, unit)${ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')},
- sum(gnuRescapable(restype, 'down', grid, node, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')
and not gnuOfflineRescapable(restype, grid, node, unit)
},
+ v_reserve(restype, 'down', grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t) // (v_reserve can be used only if the unit is capable of providing a particular reserve)
) // END sum(nuRescapable)
/ p_stepLength(m, f, t)
......@@ -2532,7 +2691,7 @@ q_boundCyclic(gnss_bound(gn_state(grid, node), s_, s), m)
and tSolveFirst = mSettings(m, 't_start')
}..
// Initial value of the state of the node at the start of the sample
// Initial value of the state of the node at the start of the sample s
+ sum(mst_start(m, s, t),
+ sum(sft(s, f, t),
+ v_state(grid, node, s, f+df(f,t+dt(t)), t+dt(t))
......@@ -2541,12 +2700,28 @@ q_boundCyclic(gnss_bound(gn_state(grid, node), s_, s), m)
=E=
// State of the node at the end of the sample
+ sum(mst_end(m, s_, t_),
+ sum(sft(s_, f_, t_),
+ v_state(grid, node, s_, f_, t_)
// Initial value of the state of the node at the start of the sample s_
+ sum(mst_start(m, s_, t),
+ sum(sft(s_, f, t),
+ v_state(grid, node, s_, f+df(f,t+dt(t)), t+dt(t))
) // END sum(ft)
) // END sum(mst_end)
) // END sum(mst_start)