Commit d205428e authored by Niina Helistö's avatar Niina Helistö
Browse files

Merge branch 'LossOfLargestInterconnector' into dev. Includes updates related...

Merge branch 'LossOfLargestInterconnector' into dev. Includes updates related to issues #69, #90, #91.
parents b19561d7 ee281454
......@@ -24,6 +24,9 @@ Model building /
q_balance
* q_resDemand
* q_resDemandLargestInfeedUnit
* q_rateOfChangeOfFrequencyUnit
* q_rateOfChangeOfFrequencyTransfer
* q_resDemandLargestInfeedTransfer
// Unit Operation
q_maxDownward
......@@ -34,6 +37,8 @@ Model building /
* q_reserveProvisionOnline
* q_startup
* q_startuptype
* q_onlineOnStartUp
* q_offlineAfterShutDown
* q_onlineLimit
* q_onlineMinUptime
* q_onlineCyclic
......@@ -80,6 +85,7 @@ Model building /
* q_emissioncap
* q_energyShareMax
* q_energyShareMin
* q_ReserveShareMax
$ifthen exist '%input_dir%/building_additional_constraints.gms'
$$include '%input_dir%/building_additional_constraints.gms' // Declare additional constraints from the input data
......
......@@ -23,7 +23,10 @@ Model invest /
q_obj
q_balance
q_resDemand
q_resDemandLargestInfeedUnit
q_resDemandLargestInfeedUnit // Use with extra caution if there are several sub-units
q_rateOfChangeOfFrequencyUnit // Use with extra caution if there are several sub-units
q_rateOfChangeOfFrequencyTransfer
q_resDemandLargestInfeedTransfer
// Unit Operation
q_maxDownward
......@@ -82,6 +85,7 @@ Model invest /
q_emissioncap
q_energyShareMax
q_energyShareMin
q_ReserveShareMax
$ifthen exist '%input_dir%/invest_additional_constraints.gms'
$$include '%input_dir%/invest_additional_constraints.gms' // Declare additional constraints from the input data
......
......@@ -23,7 +23,10 @@ Model schedule /
q_obj
q_balance
q_resDemand
q_resDemandLargestInfeedUnit
q_resDemandLargestInfeedUnit // Use with extra caution if there are several sub-units
q_rateOfChangeOfFrequencyUnit // Use with extra caution if there are several sub-units
q_rateOfChangeOfFrequencyTransfer
q_resDemandLargestInfeedTransfer
// Unit Operation
q_maxDownward
......@@ -82,6 +85,7 @@ Model schedule /
* q_emissioncap
* q_energyShareMax
* q_energyShareMin
q_ReserveShareMax
$ifthen exist '%input_dir%/schedule_additional_constraints.gms'
$$include '%input_dir%/schedule_additional_constraints.gms' // Declare additional constraints from the input data
......
......@@ -99,6 +99,10 @@ v_help_inc
q_obj
q_balance
q_resDemand
q_resDemandLargestInfeedUnit
q_rateOfChangeOfFrequencyUnit
q_rateOfChangeOfFrequencyTransfer
q_resDemandLargestInfeedTransfer
// Unit Operation
q_maxDownward
......
......@@ -134,10 +134,11 @@ r_resTransferLeftward
// Interesting reserve results
r_resDemandMarginal
r_nuTotalReserve
r_nuTotalReserveShare
r_nTotalReserve
r_gnuTotalReserve
r_gnuTotalReserveShare
r_groupTotalReserve
r_resDemandLargestInfeedUnit
* --- Investment Result Symbols -----------------------------------------------
// Interesting investment results
......@@ -152,7 +153,7 @@ r_gnTotalqGen
r_gTotalqGen
r_qResDemand
r_qResMissing
r_nTotalqResDemand
r_groupTotalqResDemand
r_qCapacity
r_solveStatus
......
......@@ -214,6 +214,8 @@ param_gn "Possible parameters for grid, node" /
boundStartToEnd "Force the last states to equal the first state"
forecastLength "Length of forecasts in use for the node (hours). After this, the node will use the central forecast."
capacityMargin "Capacity margin used in invest mode (MW)"
* defaultFrequency "default frequency for each node"
* ROCOF "Rate of change of frequency"
/
param_gnBoundaryTypes "Types of boundaries that can be set for a node with a state variable" /
......@@ -247,6 +249,7 @@ param_gnn "Set of possible data parameters for grid, node, node (nodal interconn
unitSize "Size of one link for integer investments (MW)"
invCost "Investment cost (EUR/MW)"
annuity "Investment annuity"
portion_of_transfer_to_reserve "Portion to cover incase fail"
/
param_gnu "Set of possible data parameters for grid, node, unit" /
......@@ -353,6 +356,8 @@ param_policy "Set of possible data parameters for grid, node, regulation" /
constrainedOnlineMultiplier "Multiplier a(i) for online units in equation Sum(i, a(i)*v_online(i)) <= b"
constrainedOnlineTotalMax "Total maximum b for online units in equation Sum(i, a(i)*v_online(i)) <= b"
minCons "minimum consumption of storage unit when charging"
ROCOF "Maximum rate of change of frequency (Hz/s)"
defaultFrequency "Nominal frequency in the system (Hz)"
// Reserve related parameters, currently without a proper parameter set
update_frequency "Frequency of updating reserve contributions"
update_offset "Optional offset for delaying the reserve update frequency"
......@@ -363,6 +368,8 @@ param_policy "Set of possible data parameters for grid, node, regulation" /
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
/
* --- Efficiency Approximation Related Sets -----------------------------------
......
......@@ -56,6 +56,7 @@ Sets
flowNode(flow, node) "Nodes with flows"
* --- Sets bounding geography and units ---------------------------------------
group "A group of units, transfer links, nodes, etc."
gn(grid, node) "Grids and their nodes"
* NOTE! Should it be possible to permit time-series form upper or lower bounds on states? If so, then gn() needs rethinking.
gn2n(grid, node, node) "All (directional) transfer links between nodes in specific energy grids"
......@@ -77,10 +78,12 @@ Sets
* --- Reserve types -----------------------------------------------------------
restype "Reserve types"
restypeDirection(restype, up_down) "Different combinations of reserve types and directions"
restypeDirectionNode(restype, up_down, node) "Nodes with reserve requirements"
resTypeDirectionNodeNode(restype, up_down, node, node) "Node node connections that can transfer reserves"
nuRescapable(restype, up_down, node, unit) "Units capable and available to provide particular reserves"
nuOfflineRescapable(restype, node, unit) "Units capable and available to provide offline reserves"
restypeDirectionGridNode(restype, up_down, grid, node) "Nodes with reserve requirements"
resTypeDirectionGridNodeNode(restype, up_down, grid, node, node) "Node node connections that can transfer reserves"
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"
......@@ -105,7 +108,7 @@ Sets
ft(f, t) "Combination of forecasts and t:s in the current solve"
ft_realized(f, t) "Realized ft"
ft_realizedNoReset(f, t) "Full set of realized ft, facilitates calculation of results"
ft_reservesFixed(node, restype, f, t) "Forecast-time steps with reserves fixed due to commitments on a previous solve."
ft_reservesFixed(group, restype, f, t) "Forecast-time steps with reserves fixed due to commitments on a previous solve."
mft(mType, f, t) "Combination of forecasts and t:s in the current model solve"
msf(mType, s, f) "Combination of samples and forecasts in the models"
msft(mType, s, f, t) "Combination of models, samples, forecasts and t's"
......@@ -143,7 +146,7 @@ $if defined scenario
uft_startupTrajectory(unit, f, t) "Units with start-up trajectories on intervals"
uft_shutdownTrajectory(unit, f, t) "Units with shutdown trajectories on intervals"
uft_aggregator_first(unit, f, t) "The first intervals when aggregator units are active"
nuft(node, unit, f, t) "Enables aggregation of nodes and units for later intervals"
* nuft(node, unit, f, t) "Enables aggregation of nodes and units for later intervals"
gnuft(grid, node, unit, f, t) "Enables aggregation of nodes and units for later intervals"
gnuft_ramp(grid, node, unit, f, t) "Units with ramp requirements or costs"
gnuft_rampCost(grid, node, unit, slack, f, t) "Units with ramp costs"
......@@ -159,7 +162,6 @@ $if defined scenario
shutdownCounter(unit, counter) "Counter used for unit shutdown intervals"
* --- Sets used for grouping of units, transfer links, nodes, etc. ------------
group "A group of units, transfer links, nodes, etc."
uGroup(unit, group) "Units in particular groups"
gnuGroup(grid, node, unit, group) "Combination of grids, nodes and units in particular groups"
gn2nGroup(grid, node, node, group) "Transfer links in particular groups"
......@@ -183,8 +185,7 @@ alias(f, f_, f__);
alias(s, s_, s__);
alias(grid, grid_, grid_output);
alias(unit, unit_);
alias(node, from_node, to_node, node_, node_input, node_output);
alias(node, from_node, to_node);
alias(node, from_node, to_node, node_, node_input, node_output, node_fail, node_left, node_right);
alias(effSelector, effSelector_);
alias(effDirect, effDirect_);
alias(effDirectOff, effDirectOff_);
......@@ -198,6 +199,7 @@ alias(hr, hr_, hr__);
alias(fuel, fuel_);
alias(effLevel, effLevel_);
alias(restype, restype_);
alias(group, group_);
*if(active('rampSched'),
......
......@@ -51,10 +51,13 @@ Parameters
p_gnu(grid, node, unit, param_gnu) "Unit data where energy type matters"
p_gnuBoundaryProperties(grid, node, unit, slack, param_gnuBoundaryProperties) "Properties for unit boundaries where energy type matters"
p_unit(unit, param_unit) "Unit data where energy type does not matter"
p_nReserves(node, restype, *) "Data defining the reserve rules in each node"
p_nuReserves(node, unit, restype, *) "Reserve provision data for units"
p_nnReserves(node, node, restype, up_down) "Reserve provision data for node node connections"
p_nuRes2Res(node, unit, restype, up_down, restype) "The first type of reserve can be used also in the second reserve category (with a possible multiplier)"
p_gnReserves(grid, node, restype, *) "Data defining the reserve rules in each node"
p_groupReserves(group, restype, *) "Data defining the reserve rules in each node group"
p_groupReserves3D(group, restype, up_down, param_policy) "Reserve policy in each node group separately for each reserve type and direction"
p_groupReserves4D(group, restype, up_down, group, param_policy) "Reserve policy in each node group separately for each reserve type and direction, also linking to another group"
p_gnuReserves(grid, node, unit, restype, *) "Reserve provision data for units"
p_gnnReserves(grid, node, node, restype, up_down) "Reserve provision data for node node connections"
p_gnuRes2Res(grid, node, unit, restype, up_down, restype) "The first type of reserve can be used also in the second reserve category (with a possible multiplier)"
p_gnPolicy(grid, node, param_policy, *) "Policy data for grid, node"
p_groupPolicy(group, param_policy) "Two-dimensional policy data for groups"
p_groupPolicy3D(group, param_policy, *) "Three-dimensional policy data for groups"
......@@ -124,7 +127,8 @@ Parameters
// Forecast displacement arrays
df(f, t) "Displacement needed to reach the realized forecast on the current time step"
df_central(f, t) "Displacement needed to reach the central forecast - this is needed when the forecast tree gets reduced in dynamic equations"
df_reserves(node, restype, f, t) "Forecast index displacement needed to reach the realized forecast when committing reserves"
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"
......@@ -151,7 +155,7 @@ Parameters
// Used mostly for raw data storage
ts_influx(grid, node, f, t) "External power inflow/outflow during a time step (MWh/h)"
ts_cf(flow, node, f, t) "Available capacity factor time series (p.u.)"
ts_reserveDemand(restype, up_down, node, f, t) "Reserve demand in region in the time step (MW)"
ts_reserveDemand(restype, up_down, group, f, t) "Reserve demand in region in the time step (MW)"
ts_node(grid, node, param_gnBoundaryTypes, f, t) "Fix the states of a node according to time-series form exogenous input ([v_state])"
ts_fuelPriceChange(fuel, t) "Initial fuel price and consequent changes in fuel price (EUR/MWh)"
ts_fuelPrice(fuel, t) "Fuel price time series (EUR/MWh)"
......@@ -161,7 +165,7 @@ Parameters
// NOTE: Sample dimension has to be last because of the scenario reduction algorithm
ts_influx_(grid, node, s, f, t) "Mean external power inflow/outflow during a time step (MWh/h)"
ts_cf_(flow, node, s, f, t) "Mean available capacity factor time series (p.u.)"
ts_reserveDemand_(restype, up_down, node, f, t) "Mean reserve demand in region in the time step (MW)"
ts_reserveDemand_(restype, up_down, group, f, t) "Mean reserve demand in region in the time step (MW)"
ts_node_(grid, node, param_gnBoundaryTypes, s, f, t) "Mean value of ts_node"
ts_fuelPrice_(fuel, t) "Mean fuel price time during time step (EUR/MWh)"
......@@ -171,7 +175,7 @@ Parameters
ts_effGroupUnit_update(effSelector, unit, param_eff, f, t)
ts_influx_update(grid, node, f, t)
ts_cf_update(flow, node, f, t)
ts_reserveDemand_update(restype, up_down, node, f, t)
ts_reserveDemand_update(restype, up_down, group, f, t)
ts_node_update(grid, node, param_gnBoundaryTypes, f, t)
ts_fuelPriceChange_update(fuel, t)
ts_unavailability_update(unit, t)
......
......@@ -130,17 +130,17 @@ Parameters
* --- Reserve Provision Results -----------------------------------------------
// Reserve provision results required for model structure
r_reserve(restype, up_down, node, unit, f, t) "Unit capacity reserved for providing reserve of specific type (MW)"
r_resTransferRightward(restype, up_down, node, node, f, t) "Electricity transmission capacity from the first node to the second node reserved for providing reserves (MW)"
r_resTransferLeftward(restype, up_down, node, node, f, t) "Electricity transmission capacity from the second node to the first node reserved for providing reserves (MW)"
r_reserve2Reserve(restype, up_down, node, unit, restype, f, t) "Reserve provided for another reserve category (MW) (also included in r_reserve - this is just for debugging)"
r_reserve(restype, up_down, grid, node, unit, f, t) "Unit capacity reserved for providing reserve of specific type (MW)"
r_resTransferRightward(restype, up_down, grid, node, node, f, t) "Electricity transmission capacity from the first node to the second node reserved for providing reserves (MW)"
r_resTransferLeftward(restype, up_down, grid, node, node, f, t) "Electricity transmission capacity from the second node to the first node reserved for providing reserves (MW)"
r_reserve2Reserve(restype, up_down, grid, node, unit, restype, f, t) "Reserve provided for another reserve category (MW) (also included in r_reserve - this is just for debugging)"
// Interesting reserve results
r_resDemandMarginal(restype, up_down, node, f, t) "Marginal values of the q_resDemand equation"
r_nuTotalReserve(restype, up_down, node, unit) "Total nu reserve provision over the simulation (MW*h)"
r_nuTotalReserveShare(restype, up_down, node, unit) "Total nu/n reserve provision share over the simulation"
r_nTotalReserve(restype, up_down, node) "Total reserve provisions in nodes over the simulation (MW*h)"
r_resDemandLargestInfeedUnit(grid, f, t, restype, up_down, node) "Reserve Demand from the loss of largest infeed unit"
r_resDemandMarginal(restype, up_down, group, f, t) "Marginal values of the q_resDemand equation"
r_gnuTotalReserve(restype, up_down, grid, node, unit) "Total gnu reserve provision over the simulation (MW*h)"
r_gnuTotalReserveShare(restype, up_down, grid, node, unit) "Total gnu/group reserve provision share over the simulation"
r_groupTotalReserve(restype, up_down, group) "Total reserve provisions in groups over the simulation (MW*h)"
r_resDemandLargestInfeedUnit(restype, up_down, group, f, t) "Reserve Demand from the loss of largest infeed unit"
* --- Investment Results ------------------------------------------------------
......@@ -154,9 +154,9 @@ Parameters
r_qGen(inc_dec, grid, node, f, t) "Dummy energy generation (increase) or consumption (generation decrease) to ensure equation feasibility (MW)"
r_gnTotalqGen(inc_dec, grid, node) "Total dummy energy generation/consumption in gn over the simulation (MWh)."
r_gTotalqGen(inc_dec, grid) "Total dummy energy generation/consumption in g over the simulation (MWh)."
r_qResDemand(restype, up_down, node, f, t) "Dummy to decrease demand for a reserve (MW) before reserve commitment"
r_qResMissing(restype, up_down, node, f, t) "Dummy to decrease demand for a reserve (MW) after reserve commitment"
r_nTotalqResDemand(restype, up_down, node) "Total dummy reserve provisions in n over the simulation"
r_qResDemand(restype, up_down, group, f, t) "Dummy to decrease demand for a reserve (MW) before reserve commitment"
r_qResMissing(restype, up_down, group, f, t) "Dummy to decrease demand for a reserve (MW) after reserve commitment"
r_groupTotalqResDemand(restype, up_down, group) "Total dummy reserve provisions in the group over the simulation"
r_qCapacity(grid, node, f, t) "Dummy capacity to ensure capacity margin equation feasibility (MW)"
r_solveStatus(t, solve_info) "Information about the solve"
......
......@@ -32,6 +32,7 @@ $ifthen exist '%input_dir%/inputData.gdx'
$$loaddc unitUnitEffLevel
$$loaddc uFuel
$$loaddc effLevelGroupUnit
$$loaddc group
$$loaddc p_gn
$$loaddc p_gnn
$$loaddc p_gnu
......@@ -41,10 +42,12 @@ $ifthen exist '%input_dir%/inputData.gdx'
$$loaddc restype
$$loaddc restypeDirection
$$loaddc restypeReleasedForRealization
$$loaddc p_nReserves
$$loaddc p_nuReserves
$$loaddc p_nnReserves
$$loaddc p_nuRes2Res
$$loaddc p_groupReserves
$$loaddc p_groupReserves3D
$$loaddc p_groupReserves4D
$$loaddc p_gnuReserves
$$loaddc p_gnnReserves
$$loaddc p_gnuRes2Res
$$loaddc ts_reserveDemand
$$loaddc p_gnBoundaryPropertiesForStates
$$loaddc p_gnPolicy
......@@ -61,7 +64,6 @@ $ifthen exist '%input_dir%/inputData.gdx'
$$loaddc ts_node
$$loaddc t_invest
$$loaddc p_storageValue
$$loaddc group
$$loaddc uGroup
$$loaddc gnuGroup
$$loaddc gn2nGroup
......@@ -92,7 +94,7 @@ gngnu_constrainedOutputRatio
restype
restypeReleasedForRealization
p_gnn
p_nnReserves
p_gnnReserves
p_gnuBoundaryProperties
ts_node
ts_reserveDemand
......@@ -312,6 +314,7 @@ gn2n(grid, from_node, to_node)${ p_gnn(grid, from_node, to_node, 'transferCap
or p_gnn(grid, from_node, to_node, 'transferCapBidirectional')
or p_gnn(grid, to_node, from_node, 'transferCapBidirectional')
or p_gnn(grid, from_node, to_node, 'transferCapInvLimit')
or p_gnn(grid, from_node, to_node, 'portion_of_transfer_to_reserve')
}
= yes;
......@@ -399,63 +402,79 @@ flowNode(flow, node)${ sum((f, t), ts_cf(flow, node, f, t))
// NOTE! Reserves can be disabled through the model settings file.
// The sets are disabled in "3a_periodicInit.gms" accordingly.
// Copy data from p_groupReserves to p_gnReserves
loop(gnGroup(grid, node, group)${sum(restype, p_groupReserves(group, restype, 'reserve_length'))},
p_gnReserves(grid, node, restype, param_policy) = p_groupReserves(group, restype, param_policy);
p_gnReserves(grid, node, restype, up_down) = p_groupReserves(group, restype, up_down);
);
// Units with reserve provision capabilities
nuRescapable(restypeDirection(restype, up_down), nu(node, unit))
$ { p_nuReserves(node, unit, restype, up_down)
gnuRescapable(restypeDirection(restype, up_down), gnu(grid, node, unit))
$ { p_gnuReserves(grid, node, unit, restype, up_down)
}
= yes;
// Units with offline reserve provision capabilities
nuOfflineRescapable(restype, nu(node, unit))
$ { p_nuReserves(node, unit, restype, 'offlineReserveCapability')
gnuOfflineRescapable(restype, gnu(grid, node, unit))
$ { p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability')
}
= yes;
// Restypes with offline reserve provision possibility
offlineRes(restype)
$ {sum(nu(node, unit), p_nuReserves(node, unit, restype, 'offlineReserveCapability'))
$ {sum(gnu(grid, node, unit), p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability'))
}
= yes;
// Units with offline reserve provision possibility
offlineResUnit(unit)
$ {sum((node, restype), p_nuReserves(node, unit, restype, 'offlineReserveCapability'))
$ {sum((gn(grid, node), restype), p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability'))
}
= yes;
// Node-node connections with reserve transfer capabilities
restypeDirectionNodeNode(restypeDirection(restype, up_down), node, node_)
$ { p_nnReserves(node, node_, restype, up_down)
restypeDirectionGridNodeNode(restypeDirection(restype, up_down), gn2n(grid, node, node_))
$ { p_gnnReserves(grid, node, node_, restype, up_down)
}
= yes;
// Nodes with reserve requirements, units capable of providing reserves, or reserve capable connections
restypeDirectionNode(restypeDirection(restype, up_down), node)
$ { p_nReserves(node, restype, up_down)
or p_nReserves(node, restype, 'use_time_series')
or sum(nu(node, unit), p_nuReserves(node, unit, restype, 'portion_of_infeed_to_reserve'))
or sum(nu(node, unit), nuRescapable(restype, up_down, node, unit))
or sum(gn2n(grid, node, to_node), restypeDirectionNodeNode(restype, up_down, node, to_node))
restypeDirectionGridNode(restypeDirection(restype, up_down), gn(grid, node))
$ { p_gnReserves(grid, node, restype, up_down)
or p_gnReserves(grid, node, restype, 'use_time_series')
or sum(gnu(grid, node, unit), p_gnuReserves(grid, node, unit, restype, 'portion_of_infeed_to_reserve'))
or sum(gnu(grid, node, unit), gnuRescapable(restype, up_down, grid, node, unit))
or sum(gn2n(grid, node, to_node), restypeDirectionGridNodeNode(restype, up_down, grid, node, to_node))
}
= yes;
// Groups with reserve requirements
restypeDirectionGroup(restypeDirection(restype, up_down), group)
$ { p_groupReserves(group, restype, 'reserve_length')
}
= yes;
restypeDirectionGridNodeGroup(restypeDirection(restype, up_down), gnGroup(grid, node, group))
$ { p_groupReserves(group, restype, 'reserve_length')
}
= yes;
* --- Correct values for critical reserve related parameters ------------------
// Reserve reliability assumed to be perfect if not provided in data
p_nuReserves(nu(node, unit), restype, 'reserveReliability')
${ not p_nuReserves(node, unit, restype, 'reserveReliability')
and sum(up_down, nuRescapable(restype, up_down, node, unit))
p_gnuReserves(gnu(grid, node, unit), restype, 'reserveReliability')
${ not p_gnuReserves(grid, node, unit, restype, 'reserveReliability')
and sum(up_down, gnuRescapable(restype, up_down, grid, node, unit))
}
= 1;
// Reserve provision overlap decreases the capacity of the overlapping category
loop(restype,
p_nuReserves(nu(node, unit), restype, up_down)
${ nuRescapable(restype, up_down, node, unit) }
= p_nuReserves(node, unit, restype, up_down)
- sum(restype_${ p_nuRes2Res(node, unit, restype_, up_down, restype) },
+ p_nuReserves(node, unit, restype_, up_down)
* p_nuRes2Res(node, unit, restype_, up_down, 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)
- sum(restype_${ p_gnuRes2Res(grid, node, unit, restype_, up_down, restype) },
+ p_gnuReserves(grid, node, unit, restype_, up_down)
* p_gnuRes2Res(grid, node, unit, restype_, up_down, restype)
); // END sum(restype_)
);
......@@ -572,22 +591,35 @@ loop( unitStarttype(unit, starttypeConstrained),
* --- Check reserve related data ----------------------------------------------
// Check that reserve_length is long enough for properly commitment of reserves
loop( restypeDirectionNode(restype, up_down, node),
if(p_nReserves(node, restype, 'reserve_length') < p_nReserves(node, restype, 'update_frequency') + p_nReserves(node, restype, 'gate_closure'),
loop( restypeDirectionGridNode(restype, up_down, grid, node),
// Check that reserve_length is long enough for properly commitment of reserves
if(p_gnReserves(grid, node, restype, 'reserve_length') < p_gnReserves(grid, node, restype, 'update_frequency') + p_gnReserves(grid, node, restype, 'gate_closure'),
put log '!!! Error occurred on node ', node.tl:0 /;
put log '!!! Abort: The reserve_length parameter should be longer than update_frequency + gate_closure to fix the reserves properly!' /;
abort "The 'reserve_length' parameter should be longer than 'update_frequency' + 'gate_closure' to fix the reserves properly!"
); // END if
// Check for each restype that a node does not belong to multiple groups
if(sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group), 1) > 1,
put log '!!! Error occurred on node ', node.tl:0 /;
put log '!!! Abort: For each reserve type, a node can belong to at maximum one reserve node group!' /;
abort "For each reserve type, a node can belong to at maximum one reserve node group!"
); // END if
// Check if there are units/interconnections connected to a node that does not belong to any restypeDirectionGroup
if(sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group), 1) < 1,
put log '!!! Error occurred on node ', node.tl:0 /;
put log '!!! Abort: A node with reserve provision/transfer capability has to belong to a reserve node group!' /;
abort "A node with reserve provision/transfer capability has to belong to a reserve node group!"
); // END if
); // END loop(restypeDirectionNode)
// Check that reserve overlaps are possible
loop( (nu(node, unit), restypeDirection(restype, up_down)),
if( p_nuReserves(node, unit, restype, up_down) < 0,
loop( (gnu(grid, node, unit), restypeDirection(restype, up_down)),
if( p_gnuReserves(grid, node, unit, restype, up_down) < 0,
put log '!!! Error occurred on unit ', unit.tl:0 /;
put log '!!! Abort: Overlapping reserve capacities in p_nuRes2Res can result in excess reserve production!' /;
abort "Overlapping reserve capacities in p_nuRes2Res can result in excess reserve production!"
); // END if(p_nuReserves)
); // END loop((nu,restypeDirection))
put log '!!! Abort: Overlapping reserve capacities in p_gnuRes2Res can result in excess reserve production!' /;
abort "Overlapping reserve capacities in p_gnuRes2Res can result in excess reserve production!"
); // END if(p_gnuReserves)
); // END loop((gnu,restypeDirection))
* =============================================================================
......
......@@ -43,9 +43,9 @@ Positive variables
v_spill(grid, node, s, f, t) "Spill of energy from storage node during an interval (MWh)"
v_transferRightward(grid, node, node, s, f, t) "Average electricity transmission level from the first node to the second node during an interval (MW)"
v_transferLeftward(grid, node, node, s, f, t) "Average electricity transmission level from the second node to the first node during an interval (MW)"
v_resTransferRightward(restype, up_down, node, node, s, f, t) "Electricity transmission capacity from the first node to the second node reserved for providing reserves (MW)"
v_resTransferLeftward(restype, up_down, node, node, s, f, t) "Electricity transmission capacity from the second node to the first node reserved for providing reserves (MW)"
v_reserve(restype, up_down, node, unit, s, f, t) "Unit capacity reserved for providing reserve of specific type (MW)"
v_resTransferRightward(restype, up_down, grid, node, node, s, f, t) "Electricity transmission capacity from the first node to the second node reserved for providing reserves (MW)"
v_resTransferLeftward(restype, up_down, grid, node, node, s, f, t) "Electricity transmission capacity from the second node to the first node reserved for providing reserves (MW)"
v_reserve(restype, up_down, grid, node, unit, s, f, t) "Unit capacity reserved for providing reserve of specific type (MW)"
v_investTransfer_LP(grid, node, node, t) "Invested transfer capacity (MW)"
v_online_LP(unit, s, f, t) "Number of sub-units online for 'units' with unit commitment restrictions (LP variant)"
v_invest_LP(unit, t) "Number of invested 'sub-units' (LP variant)"
......@@ -56,8 +56,8 @@ Positive variables
Positive variables
v_stateSlack(grid, node, slack, s, f, t) "Slack variable for different v_state slack categories, permits e.g. costs for exceeding acceptable v_states (MWh, unless modified by energyCapacity parameter)"
vq_gen(inc_dec, grid, node, s, f, t) "Dummy energy generation (increase) or consumption (generation decrease) to ensure equation feasibility (MW)"
vq_resDemand(restype, up_down, node, s, f, t) "Dummy to decrease demand for a reserve (MW) before the reserve has been locked"
vq_resMissing(restype, up_down, node, s, f, t) "Dummy to decrease demand for a reserve (MW) after the reserve has been locked"
vq_resDemand(restype, up_down, group, s, f, t) "Dummy to decrease demand for a reserve (MW) before the reserve has been locked"
vq_resMissing(restype, up_down, group, s, f, t) "Dummy to decrease demand for a reserve (MW) after the reserve has been locked"
vq_capacity(grid, node, s, f, t) "Dummy variable to ensure capacity margin equation feasibility (MW)"
;
......@@ -53,15 +53,18 @@ equations
// Objective Function, Energy Balance, and Reserve demand
q_obj "Objective function"
q_balance(grid, node, mType, s, f, t) "Energy demand must be satisfied at each node"
q_resDemand(restype, up_down, node, s, f, t) "Procurement for each reserve type is greater than demand"
q_resDemandLargestInfeedUnit(grid, restype, up_down, node, unit, s, f, t) "N-1 Reserve"
q_resDemand(restype, up_down, group, s, f, t) "Procurement for each reserve type is greater than demand"
q_resDemandLargestInfeedUnit(restype, up_down, group, unit, s, f, t) "N-1 reserve for units"
q_rateOfChangeOfFrequencyUnit(group, unit, s, f, t) "N-1 unit contingency with ROCOF"
q_rateOfChangeOfFrequencyTransfer(group, grid, node, node, s, f, t) "N-1 transmission line contingency with ROCOF"
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 (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, node, unit, s, f, t) "Reserve provision limited for units with investment possibility"
q_reserveProvisionOnline(restype, up_down, node, unit, s, f, t) "Reserve provision limited for units that are not capable of providing offline reserve"
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"
......@@ -93,8 +96,8 @@ equations
q_transferLeftwardLimit(grid, node, node, s, f, t) "Transfer of energy and capacity reservations to the leftward direction are less than the transfer capacity"
q_resTransferLimitRightward(grid, node, node, s, f, t) "Transfer of energy and capacity reservations are less than the transfer capacity to the rightward direction"
q_resTransferLimitLeftward(grid, node, node, s, f, t) "Transfer of energy and capacity reservations are less than the transfer capacity to the leftward direction"
q_reserveProvisionRightward(restype, up_down, node, node, s, f, t) "Rightward reserve provision limited"
q_reserveProvisionLeftward(restype, up_down, node, node, s, f, t) "Leftward reserve provision limited"
q_reserveProvisionRightward(restype, up_down, grid, node, node, s, f, t) "Rightward reserve provision limited"
q_reserveProvisionLeftward(restype, up_down, grid, node, node, s, f, t) "Leftward reserve provision limited"
// State Variables
q_stateSlack(grid, node, slack, s, f, t) "Slack variable greater than the difference between v_state and the slack boundary"
......@@ -112,4 +115,5 @@ equations
q_emissioncap(group, emission) "Limit for emissions"
q_energyShareMax(group) "Maximum energy share of generation and import from a group of units"
q_energyShareMin(group) "Minimum energy share of generation and import from a group of units"
q_ReserveShareMax(group, restype, up_down, group, s, f, t) "Maximum reserve share of a group of units"
;
......@@ -72,10 +72,10 @@ q_obj ..
) // END sum(inc_dec)
// Reserve provision feasibility dummy variable penalties
+ sum(restypeDirectionNode(restype, up_down, node),
+ vq_resDemand(restype, up_down, node, s, f+df_reserves(node, restype, f, t), t)
+ sum(restypeDirectionGroup(restype, up_down, group),
+ vq_resDemand(restype, up_down, group, s, f+df_reservesGroup(group, restype, f, t), t)
* PENALTY_RES(restype, up_down)
+ vq_resMissing(restype, up_down, node, s, f+df_reserves(node, restype, f, t), t)${ ft_reservesFixed(node, restype, f+df_reserves(node, restype, f, t), t) }
+ vq_resMissing(restype, up_down, group, s, f+df_reservesGroup(group, restype, f, t), t)${ ft_reservesFixed(group, restype, f+df_reservesGroup(group, restype, f, t), t) }
* PENALTY_RES_MISSING(restype, up_down)
) // END sum(restypeDirectionNode)
......
......@@ -90,134 +90,365 @@ q_balance(gn(grid, node), msft(m, s, f, t)) // Energy/power balance dynamics sol
// NOTE! Currently, there are multiple identical instances of the reserve balance equation being generated for each forecast branch even when the reserves are committed and identical between the forecasts.
// NOTE! This could be solved by formulating a new "ft_reserves" set to cover only the relevant forecast-time steps, but it would possibly make the reserves even more confusing.
q_resDemand(restypeDirectionNode(restype, up_down, node), sft(s, f, t))
${ ord(t) < tSolveFirst + p_nReserves(node, restype, 'reserve_length')
q_resDemand(restypeDirectionGroup(restype, up_down, group), sft(s, f, t))
${ ord(t) < tSolveFirst + p_groupReserves(group, restype, 'reserve_length')
and not [ restypeReleasedForRealization(restype)
and sft_realized(s, f, t)]
} ..
// Reserve provision by capable units on this node
+ sum(nuft(node, unit, f, t)${nuRescapable(restype, up_down, node, unit)},
+ v_reserve(restype, up_down, node, unit, s, f+df_reserves(node, restype, f, t), t)
// Reserve provision by capable units on this group
+ sum(gnuft(grid, node, unit, f, t)${ gnGroup(grid, node, group)
and gnuRescapable(restype, up_down, grid, node, unit)
},
+ v_reserve(restype, up_down, grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t)
* [ // Account for reliability of reserves
+ 1${sft_realized(s, f+df_reserves(node, restype, f, t), t)} // reserveReliability limits the reliability of reserves locked ahead of time.
+ p_nuReserves(node, unit, restype, 'reserveReliability')${not sft_realized(s, f+df_reserves(node, restype, f, t), t)}
+ 1${sft_realized(s, f+df_reserves(grid, node, restype, f, t), t)} // reserveReliability limits the reliability of reserves locked ahead of time.
+ p_gnuReserves(grid, node, unit, restype, 'reserveReliability')${not sft_realized(s, f+df_reserves(grid, node, restype, f, t), t)}
] // END * v_reserve
) // END sum(nuft)
) // END sum(gnuft)
// Reserve provision from other reserve categories when they can be shared
+ sum((nuft(node, unit, f, t), restype_)${p_nuRes2Res(node, unit, restype_, up_down, restype)},
+ v_reserve(restype_, up_down, node, unit, s, f+df_reserves(node, restype_, f, t), t)
* p_nuRes2Res(node, unit, restype_, up_down, restype)
+ sum((gnuft(grid, node, unit, f, t), restype_)${ gnGroup(grid, node, group)
and p_gnuRes2Res(grid, node, unit, restype_, up_down, restype)
},
+ v_reserve(restype_, up_down, grid, node, unit, s, f+df_reserves(grid, node, restype_, f, t), t)
* p_gnuRes2Res(grid, node, unit, restype_, up_down, restype)
* [ // Account for reliability of reserves
+ 1${sft_realized(s, f+df_reserves(node, restype, f, t), t)} // reserveReliability limits the reliability of reserves locked ahead of time.
+ p_nuReserves(node, unit, restype, 'reserveReliability')${not sft_realized(s, f+df_reserves(node, restype, f, t), t)}
* p_nuReserves(node, unit, restype_, 'reserveReliability')
+ 1${sft_realized(s, f+df_reserves(grid, node, restype, f, t), t)} // reserveReliability limits the reliability of reserves locked ahead of time.
+ p_gnuReserves(grid, node, unit, restype, 'reserveReliability')${not sft_realized(s, f+df_reserves(grid, node, restype, f, t), t)}
* p_gnuReserves(grid, node, unit, restype_, 'reserveReliability')
] // END * v_reserve
) // END sum(nuft)
) // END sum(gnuft)
// Reserve provision to this node via transfer links
+ sum(gn2n_directional(grid, node_, node)${restypeDirectionNodeNode(restype, up_down, node_, node)},
// Reserve provision to this group via transfer links
+ sum(gn2n_directional(grid, node_, node)${ gnGroup(grid, node, group)
and not gnGroup(grid, node_, group)
and restypeDirectionGridNodeNode(restype, up_down, grid, node_, node)
},
+ (1 - p_gnn(grid, node_, node, 'transferLoss') )
* v_resTransferRightward(restype, up_down, node_, node, s, f+df_reserves(node_, restype, f, t), t) // Reserves from another node - reduces the need for reserves in the node
* v_resTransferRightward(restype, up_down, grid, node_, node, s, f+df_reserves(grid, node_, restype, f, t), t) // Reserves from another node - reduces the need for reserves in the node
) // END sum(gn2n_directional)
+ sum(gn2n_directional(grid, node, node_)${restypeDirectionNodeNode(restype, up_down, node_, node)},
+ sum(gn2n_directional(grid, node, node_)${ gnGroup(grid, node, group)
and not gnGroup(grid, node_, group)
and restypeDirectionGridNodeNode(restype, up_down, grid, node_, node)
},
+ (1 - p_gnn(grid, node, node_, 'transferLoss') )
* v_resTransferLeftward(restype, up_down, node, node_, s, f+df_reserves(node_, restype, f, t), t) // Reserves from another node - reduces the need for reserves in the node
* v_resTransferLeftward(restype, up_down, grid, node, node_, s, f+df_reserves(grid, node_, restype, f, t), t) // Reserves from another node - reduces the need for reserves in the node
) // END sum(gn2n_directional)
=G=