2b_equations.gms 77.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ontext
This file is part of Backbone.

Backbone is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Backbone is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Backbone.  If not, see <http://www.gnu.org/licenses/>.
$offtext

18
19
equations
    q_obj "Objective function"
20
    q_balance(grid, node, mType, f, t) "Energy demand must be satisfied at each node"
21
    q_resDemand(restype, up_down, node, f, t) "Procurement for each reserve type is greater than demand"
22
23
24
25
26
    q_resTransferLimitRightward(grid, node, node, f, t) "Transfer of energy and capacity reservations are less than the transfer capacity to the rightward direction"
    q_resTransferLimitLeftward(grid, node, node, f, t) "Transfer of energy and capacity reservations are less than the transfer capacity to the leftward direction"
    q_transferRightwardLimit(grid, node, node, f, t) "Transfer of energy and capacity reservations to the rightward direction are less than the transfer capacity"
    q_transferLeftwardLimit(grid, node, node, f, t) "Transfer of energy and capacity reservations to the leftward direction are less than the transfer capacity"
    q_transfer(grid, node, node, f, t) "Rightward and leftward transfer must match the total transfer"
27
28
    q_maxDownward(mType, grid, node, unit, f, t) "Downward commitments will not undercut power plant minimum load constraints or maximum elec. consumption"
    q_maxUpward(mType, grid, node, unit, f, t) "Upward commitments will not exceed maximum available capacity or consumed power"
Juha Kiviluoma's avatar
Juha Kiviluoma committed
29
    q_startup(unit, f, t) "Capacity started up is greater than the difference of online cap. now and in the previous time step"
Niina Helistö's avatar
Niina Helistö committed
30
31
    q_genRamp(grid, node, mType, s, unit, f, t) "Record the ramps of units with ramp restricitions or costs"
    q_genRampChange(grid, node, mType, s, unit, f, t) "Record the ramp rates of units with ramping costs"
32
    q_conversionDirectInputOutput(effSelector, unit, f, t) "Direct conversion of inputs to outputs (no piece-wise linear part-load efficiencies)"
Juha Kiviluoma's avatar
Juha Kiviluoma committed
33
34
35
36
37
    q_conversionSOS2InputIntermediate(effSelector, unit, f, t)   "Intermediate output when using SOS2 variable based part-load piece-wise linearization"
    q_conversionSOS2Constraint(effSelector, unit, f, t)          "Sum of v_sos2 has to equal v_online"
    q_conversionSOS2IntermediateOutput(effSelector, unit, f, t)  "Output is forced equal with v_sos2 output"
    q_outputRatioFixed(grid, node, grid, node, unit, f, t) "Force fixed ratio between two energy outputs into different energy grids"
    q_outputRatioConstrained(grid, node, grid, node, unit, f, t) "Constrained ratio between two grids of energy output; e.g. electricity generation is greater than cV times unit_heat generation in extraction plants"
38
    q_stateSlack(grid, node, slack, f, t) "Slack variable greater than the difference between v_state and the slack boundary"
39
40
    q_stateUpwardLimit(grid, node, mType, f, t) "Limit the commitments of a node with a state variable to the available headrooms"
    q_stateDownwardLimit(grid, node, mType, f, t) "Limit the commitments of a node with a state variable to the available headrooms"
41
    q_boundState(grid, node, node, mType, f, t) "Node state variables bounded by other nodes"
42
    q_boundStateMaxDiff(grid, node, node, mType, f, t) "Node state variables bounded by other nodes (maximum state difference)"
43
    q_boundCyclic(grid, node, mType, f, t, f_, t_) "Cyclic bound for the first and the last state"
44
45
46
    q_fixedGenCap1U(grid, node, unit, t) "Fixed capacity ratio of a unit in one node versus all nodes it is connected to"
    q_fixedGenCap2U(grid, node, unit, grid, node, unit, t) "Fixed capacity ratio of two (grid, node, unit) pairs"
    q_onlineLimit(unit, f, t) "Number of online units limited for units with investment possibility"
Niina Helistö's avatar
Niina Helistö committed
47
48
    q_rampUpLimit(grid, node, mType, s, unit, f, t) "Up ramping limited for units"
    q_rampDownLimit(grid, node, mType, s, unit, f, t) "Down ramping limited for units"
49
50
51
    q_startuptype(mType, unit, starttype, f, t) "Startup type depends on the time the unit has been non-operational"
    q_minUp(mType, unit, f, t) "Unit must stay operational if it has started up during the previous minOperationTime hours"
    q_minDown(mType, unit, f, t) "Unit must stay non-operational if it has shut down during the previous minShutDownTime hours"
52
    q_capacityMargin(grid, node, f, t) "There needs to be enough capacity to cover energy demand plus a margin"
Niina Helistö's avatar
Niina Helistö committed
53
    q_emissioncap(gngroup, emission) "Limit for emissions"
54
    q_instantaneousShareMax(gngroup, group, f, t) "Maximum instantaneous share of generation and controlled import from a group of units and links"
Niina Helistö's avatar
Niina Helistö committed
55
56
    q_energyShareMax(gngroup, group) "Maximum energy share of generation and import from a group of units"
    q_energyShareMin(gngroup, group) "Minimum energy share of generation and import from a group of units"
57
    q_boundCyclicSamples(grid, node, mType, s, f, t, s_, f_, t_) "Cyclic bound inside or between samples"
58
    q_inertiaMin(gngroup, f, t) "Minimum inertia in a group of nodes"
59
60
;

61
$setlocal def_penalty 1e6
62
63
64
65
Scalars
    PENALTY "Default equation violation penalty" / %def_penalty% /
;
Parameters
66
    PENALTY_BALANCE(grid) "Penalty on violating energy balance eq. (/MWh)"
67
    PENALTY_RES(restype, up_down) "Penalty on violating a reserve (/MW)"
68
;
69
PENALTY_BALANCE(grid) = %def_penalty%;
70
PENALTY_RES(restype, up_down) = %def_penalty%;
71
72
73

* -----------------------------------------------------------------------------
q_obj ..
Juha Kiviluoma's avatar
Juha Kiviluoma committed
74
  + v_obj * 1000000
75
76
  =E=
  + sum(msft(m, s, f, t),
77
        p_sft_Probability(s,f,t) *
78
        (
79
80
         // Variable O&M costs
         + sum(gnu_output(grid, node, unit),  // Calculated only for output energy
Juha Kiviluoma's avatar
Juha Kiviluoma committed
81
                p_unit(unit, 'omCosts') *
82
83
                $$ifi not '%rampSched%' == 'yes' p_stepLength(m, f, t) *
                $$ifi '%rampSched%' == 'yes' (p_stepLength(m, f, t) + p_stepLength(m, f, t+1))/2 *
84
                     v_gen(grid, node, unit, f, t)$nuft(node, unit, f, t)
85
           )
86

87
         // Fuel and emission costs
88
         + sum((uft(unit_fuel, f, t), fuel)$uFuel(unit_fuel, 'main', fuel),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
89
              + p_stepLength(m, f, t)
90
              * v_fuelUse(fuel, unit_fuel, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
91
                  * ( + sum{tFuel$[ord(tFuel) <= ord(t)],
92
93
94
                           ts_fuelPriceChange(fuel, tFuel) }  // Fuel costs, sum initial fuel price plus all subsequent changes to the fuelprice
                      + sum(emission,                          // Emission taxes
                           p_unitFuelEmissionCost(unit_fuel, fuel, emission) )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
95
96
                     )
           )
97

98
         // Start-up costs
99
         + sum(uft_online(unit, f, t),
100
             + sum(starttype,
101
102
103
104
105
106
107
108
109
110
111
112
113
                 + {
                     + v_startup(unit, starttype, f, t) // Cost of starting up
                   }
                 * {
                     // Startup variable costs
                     + p_uStartup(unit, starttype, 'cost', 'unit')${not unit_investLP(unit)}
                     + p_uStartup(unit, starttype, 'cost', 'capacity')$unit_investLP(unit)
                     // Start-up fuel and emission costs
                     + sum(uFuel(unit, 'startup', fuel)$unit_fuel(unit),
                         + (
                             + p_uStartup(unit, starttype, 'consumption', 'unit')${not unit_investLP(unit)}
                             + p_uStartup(unit, starttype, 'consumption', 'capacity')$unit_investLP(unit)
                           )
114
                         * ( + sum{tFuel$[ord(tFuel) <= ord(t)],
115
                                   ts_fuelPriceChange(fuel, tFuel) }  // Fuel costs for start-up fuel use
116
                             + sum(emission,                          // Emission taxes of startup fuel use
117
                                   p_unitFuelEmissionCost(unit, fuel, emission) )
118
                           )
119
120
                       )
                   }
121
               )
122
           )
123

124
         // Ramping costs
125
126
127
128
129
130
131
132
         + sum(gnuft_ramp(grid, node, unit, f, t)${ [ p_gnu(grid, node, unit, 'rampUpCost')
                                                      or p_gnu(grid, node, unit, 'rampDownCost')
                                                      ]
                                                    and ord(t) > mSettings(m, 't_start')
                                                  },
            + ( // Changes in ramp rates
                + p_gnu(grid, node, unit, 'rampUpCost') * v_genRampChange(grid, node, unit, 'up', f, t+pt(t))
                + p_gnu(grid, node, unit, 'rampDownCost') * v_genRampChange(grid, node, unit, 'down', f, t+pt(t))
133
134
              )
           )
135

136
         // "Cost" of spilling energy, !!! TEMPORARY MEASURES !!!
137
         + sum(gn(grid, node_spill(node)),
138
            + v_spill(grid, node, f, t) * p_stepLength(m, f, t)
139
140
           )

141
        )  // END * p_sft_probability(s,f,t)
142
    ) // END sum over msft(m, s, f, t)
143
*$ontext
Topi Rasku's avatar
Topi Rasku committed
144

145
146
147
148
149
150
151
152
153
154
155
156
    // Node state slack variable penalties
  + sum(gn_stateSlack(grid, node),
    + sum(ms(m, s),
      + sum(ft_full(f, t)${ ft_dynamic(f,t) or mftStart(m, f, t) },
        + p_stepLength(m, f, t)
        * p_sft_probability(s, f, t)
        * sum(slack${p_gnBoundaryPropertiesForStates(grid, node, slack, 'slackCost')},
                + p_gnBoundaryPropertiesForStates(grid, node, slack, 'slackCost') * v_stateSlack(grid, node, slack, f, t)
            )
        )
      )
    )
157
    // Value of energy storage change
158
159
  - sum(mftLastSteps(m, f, t)$active('storageValue'),
         sum(gn_state(grid, node),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
160
              p_storageValue(grid, node, t) *
161
162
163
164
165
166
167
                  sum(s$p_sft_probability(s,f,t), p_sft_probability(s, f, t) * v_state(grid, node, f, t))
         )
    )
  + sum(mftStart(m, f, t)$active('storageValue'),
         sum(gn_state(grid, node),
              p_storageValue(grid, node, t) *
                  sum(s$p_sft_probability(s,f,t), p_sft_probability(s, f, t) * v_state(grid, node, f, t))
168
         ) * mSettings(m, 'forecasts') // Correction for the smaller amount of starting points
Juha Kiviluoma's avatar
Juha Kiviluoma committed
169
    )
170
171
$ontext
    // "Value" of online units, !!! TEMPORARY MEASURES !!!
172
  - sum([s, m, uft_online(unit, ft_dynamic(f,t))]$mftStart(m, f, t),
173
174
175
176
177
        + p_sft_probability(s, f, t) * 0.5
            * (
                + v_online(unit, f+cf(f,t), t) * p_unit(unit, 'startCost')
                + v_online_LP(unit, f+cf(f,t), t) * p_unit(unit, 'startCost_MW')
              )
178
        ) // minus value of avoiding startup costs before
179
  - sum((s, uft_online_last(unit, ft_dynamic(f,t))),
180
181
182
183
184
        + p_sft_probability(s, f, t) * 0.5
            * (
                + v_online(unit, f+cf(f,t), t) * p_unit(unit, 'startCost')
                + v_online_LP(unit, f+cf(f,t), t) * p_unit(unit, 'startCost_MW')
              )
185
        ) // or after the model solve
186
187
$offtext
    // Dummy variable penalties
188
  + sum(msft(m, s, f, t), p_sft_probability(s, f, t) * (
Juha Kiviluoma's avatar
Juha Kiviluoma committed
189
        sum(inc_dec,
190
            sum( gn(grid, node), vq_gen(inc_dec, grid, node, f, t) * (p_stepLength(m, f, t) + p_stepLength(m, f+pf(f,t), t+pt(t))${not p_stepLength(m, f, t)}) * PENALTY_BALANCE(grid) )
191
        )
192
193
        + sum(restypeDirectionNode(restype, up_down, node),
              vq_resDemand(restype, up_down, node, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
194
            * p_stepLength(m, f, t)
195
            * PENALTY_RES(restype, up_down)
196
197
          )
      )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
198
    )
199
200
201
202
203
204
205
206
207
  + sum(t_invest(t),
      // Unit investment costs
    + sum(gnu(grid, node, unit),
        + v_invest_LP(grid, node, unit, t) * p_gnu(grid, node, unit, 'invCosts')
            * p_gnu(grid, node, unit, 'annuity')
        + v_invest_MIP(unit, t) * p_gnu(grid, node, unit, 'unitSizeTot')
            * p_gnu(grid, node, unit, 'invCosts') * p_gnu(grid, node, unit, 'annuity')
      )
      // Transfer link investment costs
208
209
210
211
212
213
214
215
216
217
218
    + sum(gn2n_directional(grid, from_node, to_node),
        + v_investTransfer_LP(grid, from_node, to_node, t)
            * (
                + p_gnn(grid, from_node, to_node, 'invCost') * p_gnn(grid, from_node, to_node, 'annuity')
                + p_gnn(grid, to_node, from_node, 'invCost') * p_gnn(grid, to_node, from_node, 'annuity')
              )
        + v_investTransfer_MIP(grid, from_node, to_node, t)
            * (
                + p_gnn(grid, from_node, to_node, 'unitSize') * p_gnn(grid, from_node, to_node, 'invCost') * p_gnn(grid, from_node, to_node, 'annuity')
                + p_gnn(grid, to_node, from_node, 'unitSize') * p_gnn(grid, to_node, from_node, 'invCost') * p_gnn(grid, to_node, from_node, 'annuity')
              )
219
      )
220
    )
221
;
222

223
* -----------------------------------------------------------------------------
224
225
226
q_balance(gn(grid, node), m, ft_dynamic(f, t))${    p_stepLength(m, f+pf(f,t), t+pt(t))
                                                    and not p_gn(grid, node, 'boundAll')
                                                } .. // Energy/power balance dynamics solved using implicit Euler discretization
227
  // The left side of the equation is the change in the state (will be zero if the node doesn't have a state)
228
  + p_gn(grid, node, 'energyStoredPerUnitOfState')$gn_state(grid, node) // Unit conversion between v_state of a particular node and energy variables (defaults to 1, but can have node based values if e.g. v_state is in Kelvins and each node has a different heat storage capacity)
229
      * ( + v_state(grid, node, f+cf_Central(f,t), t)                   // The difference between current
230
          - v_state(grid, node, f+pf(f,t), t+pt(t))                     // ... and previous state of the node
231
232
233
234
235
236
        )
  =E=
  // The right side of the equation contains all the changes converted to energy terms
  + (
      + (
          // Self discharge out of the model boundaries
237
          - p_gn(grid, node, 'selfDischargeLoss')$gn_state(grid, node) * (
238
              + v_state(grid, node, f+cf_Central(f,t), t)                                               // The current state of the node
239
              $$ifi '%rampSched%' == 'yes' + v_state(grid, node, f+pf(f,t), t+pt(t))    // and possibly averaging with the previous state of the node
240
241
242
243
            )
          // Energy diffusion from this node to neighbouring nodes
          - sum(to_node$(gnn_state(grid, node, to_node)),
              + p_gnn(grid, node, to_node, 'diffCoeff') * (
244
                  + v_state(grid, node, f+cf_Central(f,t), t)
245
246
247
248
249
250
                  $$ifi '%rampSched%' == 'yes' + v_state(grid, node, f+pf(f,t), t+pt(t))
                )
            )
          // Energy diffusion from neighbouring nodes to this node
          + sum(from_node$(gnn_state(grid, from_node, node)),
              + p_gnn(grid, from_node, node, 'diffCoeff') * (
251
                  + v_state(grid, from_node, f+cf_Central(f,t), t)                                             // Incoming diffusion based on the state of the neighbouring node
252
253
254
                  $$ifi '%rampSched%' == 'yes' + v_state(grid, from_node, f+pf(f,t), t+pt(t))  // Ramp schedule averaging, NOTE! State and other terms use different indeces for non-ramp-schedule!
                )
            )
255
256
257
258
259
260
261
262
263
          // Controlled energy transfer, applies when the current node is on the left side of the connection
          - sum(node_$(gn2n_directional(grid, node, node_)),
              + (1 - p_gnn(grid, node, node_, 'transferLoss')) * (    // Reduce transfer losses
                  + v_transfer(grid, node, node_, f, t+pt(t))
                  $$ifi '%rampSched%' == 'yes' + v_transfer(grid, node, node_, f, t)    // Ramp schedule averaging, NOTE! State and other terms use different indeces for non-ramp-schedule!
                )
              + (p_gnn(grid, node, node_, 'transferLoss')) * (    // Add transfer losses back if transfer is from this node to another node
                  + v_transferRightward(grid, node, node_, f, t+pt(t))
                  $$ifi '%rampSched%' == 'yes' + v_transferRightward(grid, node, node_, f, t)    // Ramp schedule averaging, NOTE! State and other terms use different indeces for non-ramp-schedule!
264
265
                )
            )
266
267
268
269
270
271
272
273
274
275
          // Controlled energy transfer, applies when the current node is on the right side of the connection
          + sum(node_$(gn2n_directional(grid, node_, node)),
              + (
                  + v_transfer(grid, node_, node, f, t+pt(t))
                  $$ifi '%rampSched%' == 'yes' + v_transfer(grid, node_, node, f, t)    // Ramp schedule averaging, NOTE! State and other terms use different indeces for non-ramp-schedule!
                )
              - (p_gnn(grid, node_, node, 'transferLoss')) * (    // Reduce transfer losses if transfer is from another node to this node
                  + v_transferRightward(grid, node_, node, f, t+pt(t))
                  $$ifi '%rampSched%' == 'yes' + v_transferRightward(grid, node_, node, f, t)    // Ramp schedule averaging, NOTE! State and other terms use different indeces for non-ramp-schedule!
                )
276
277
            )
          // Interactions between the node and its units
278
279
280
          + sum(gnuft(grid, node, unit, f, t+pt(t)),
              + v_gen(grid, node, unit, f, t+pt(t)) // Unit energy generation and consumption
              $$ifi '%rampSched%' == 'yes' + v_gen(grid, node, unit, f, t)
281
282
            )
          // Spilling energy out of the endogenous grids in the model
283
284
          - v_spill(grid, node, f, t+pt(t))$node_spill(node)
          $$ifi '%rampSched%' == 'yes' - v_spill(grid, node, f, t)$node_spill(node)
285
          // Power inflow and outflow timeseries to/from the node
286
287
          + ts_influx_(grid, node, f, t+pt(t))   // Incoming (positive) and outgoing (negative) absolute value time series
          $$ifi '%rampSched%' == 'yes' + ts_influx_(grid, node, f, t)
288
          // Dummy generation variables, for feasibility purposes
289
290
291
292
          + vq_gen('increase', grid, node, f, t+pt(t)) // Note! When stateSlack is permitted, have to take caution with the penalties so that it will be used first
          $$ifi '%rampSched%' == 'yes' + vq_gen('increase', grid, node, f, t)
          - vq_gen('decrease', grid, node, f, t+pt(t)) // Note! When stateSlack is permitted, have to take caution with the penalties so that it will be used first
          $$ifi '%rampSched%' == 'yes' - vq_gen('decrease', grid, node, f, t)
293
294
295
        ) * p_stepLength(m, f+pf(f,t), t+pt(t))   // Multiply by time step to get energy terms
    )
  $$ifi '%rampSched%' == 'yes' / 2    // Averaging all the terms on the right side of the equation over the timestep here.
296
297
;
* -----------------------------------------------------------------------------
298
q_resDemand(restypeDirectionNode(restype, up_down, node), ft(f, t))${   ord(t) < tSolveFirst + sum[mf(m, f), mSettings(m, 't_reserveLength')]
Topi Rasku's avatar
Topi Rasku committed
299
                                                                        } ..
300
  + sum(nuft(node, unit, f, t)${nuRescapable(restype, up_down, node, unit)},   // Reserve capable units on this node
301
        v_reserve(restype, up_down, node, unit, f+cf_nReserves(node, restype, f, t), t)
302
    )
303
304
305
306
307
308
309
  + sum(gn2n_directional(grid, node_, node)${restypeDirectionNode(restype, up_down, node_)},
      + (1 - p_gnn(grid, node_, node, 'transferLoss')
        ) * v_resTransferRightward(restype, up_down, node_, node, f+cf_nReserves(node_, restype, f, t), t)             // Reserves from another node - reduces the need for reserves in the node
    )
  + sum(gn2n_directional(grid, node, node_)${restypeDirectionNode(restype, up_down, node_)},
      + (1 - p_gnn(grid, node, node_, 'transferLoss')
        ) * v_resTransferLeftward(restype, up_down, node, node_, f+cf_nReserves(node_, restype, f, t), t)             // Reserves from another node - reduces the need for reserves in the node
310
311
    )
  =G=
312
313
  + ts_reserveDemand_(restype, up_down, node, f, t)$p_nReserves(node, restype, 'use_time_series')
  + p_nReserves(node, restype, up_down)${not p_nReserves(node, restype, 'use_time_series')}
314
  - vq_resDemand(restype, up_down, node, f, t)
315
316
317
318
319
  + sum(gn2n_directional(grid, node, node_)${restypeDirectionNode(restype, up_down, node_)},   // If trasferring reserves to another node, increase your own reserves by same amount
      + v_resTransferRightward(restype, up_down, node, node_, f+cf_nReserves(node, restype, f, t), t)
    )
  + sum(gn2n_directional(grid, node_, node)${restypeDirectionNode(restype, up_down, node_)},   // If trasferring reserves to another node, increase your own reserves by same amount
      + v_resTransferLeftward(restype, up_down, node_, node, f+cf_nReserves(node, restype, f, t), t)
320
321
322
    )
;
* -----------------------------------------------------------------------------
323
324
325
326
327
328
329
330
331
332
333
334
q_resTransferLimitRightward(gn2n_directional(grid, node, node_), ft(f, t))${
    sum(restypeDirection(restype, 'up'), restypeDirectionNode(restype, 'up', node_))
    or sum(restypeDirection(restype, 'down'), restypeDirectionNode(restype, 'down', node))
    or p_gnn(grid, node, node_, 'transferCapInvLimit')
} ..

  + v_transfer(grid, node, node_, f, t)
  + sum(restypeDirection(restype, 'up')$(restypeDirectionNode(restype, 'up', node_)),
      + v_resTransferRightward(restype, 'up', node, node_, f+cf_nReserves(node_, restype, f, t), t)
    )
  + sum(restypeDirection(restype, 'down')$(restypeDirectionNode(restype, 'down', node)),
      + v_resTransferLeftward(restype, 'down', node, node_, f+cf_nReserves(node, restype, f, t), t)
335
336
    )
  =L=
337
  + p_gnn(grid, node, node_, 'transferCap')
338
  + sum(t_$(ord(t_)<=ord(t)),
339
340
      + v_investTransfer_LP(grid, node, node_, t_)
      + v_investTransfer_MIP(grid, node, node_, t_) * p_gnn(grid, node, node_, 'unitSize')
341
342
343
    )
;
* -----------------------------------------------------------------------------
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
q_resTransferLimitLeftward(gn2n_directional(grid, node, node_), ft(f, t))${
    sum(restypeDirection(restype, 'up'), restypeDirectionNode(restype, 'up', node_))
    or sum(restypeDirection(restype, 'down'), restypeDirectionNode(restype, 'down', node))
    or p_gnn(grid, node, node_, 'transferCapInvLimit')
} ..

  + v_transfer(grid, node, node_, f, t)
  - sum(restypeDirection(restype, 'up')$(restypeDirectionNode(restype, 'up', node)),
      + v_resTransferLeftward(restype, 'up', node, node_, f+cf_nReserves(node, restype, f, t), t)
    )
  - sum(restypeDirection(restype, 'down')$(restypeDirectionNode(restype, 'down', node_)),
      + v_resTransferRightward(restype, 'down', node, node_, f+cf_nReserves(node_, restype, f, t), t)
    )
  =G=
  - p_gnn(grid, node_, node, 'transferCap')
  - sum(t_$(ord(t_)<=ord(t)),
      + v_investTransfer_LP(grid, node, node_, t_)
      + v_investTransfer_MIP(grid, node, node_, t_) * p_gnn(grid, node, node_, 'unitSize')
362
363
364
    )
;
* -----------------------------------------------------------------------------
365
366
q_transferRightwardLimit(gn2n_directional(grid, node, node_), ft(f, t))${p_gnn(grid, node, node_, 'transferCapInvLimit')} ..
  + v_transferRightward(grid, node, node_, f, t)
367
  =L=
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
  + p_gnn(grid, node, node_, 'transferCap')
  + sum(t_$(ord(t_)<=ord(t)),
      + v_investTransfer_LP(grid, node, node_, t_)
      + v_investTransfer_MIP(grid, node, node_, t_) * p_gnn(grid, node, node_, 'unitSize')
    )
;
* -----------------------------------------------------------------------------
q_transferLeftwardLimit(gn2n_directional(grid, node, node_), ft(f, t))${p_gnn(grid, node, node_, 'transferCapInvLimit')} ..
  + v_transferLeftward(grid, node, node_, f, t)
  =L=
  + p_gnn(grid, node_, node, 'transferCap')
  + sum(t_$(ord(t_)<=ord(t)),
      + v_investTransfer_LP(grid, node, node_, t_)
      + v_investTransfer_MIP(grid, node, node_, t_) * p_gnn(grid, node, node_, 'unitSize')
    )
;
* -----------------------------------------------------------------------------
q_transfer(gn2n_directional(grid, node, node_), ft(f, t)) ..
  + v_transferRightward(grid, node, node_, f, t)
  - v_transferLeftward(grid, node, node_, f, t)
  =E=
  + v_transfer(grid, node, node_, f, t)
390
391
;
* -----------------------------------------------------------------------------
392
q_maxDownward(m, gnuft(grid, node, unit, f, t))${ [     ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')               // Unit is either providing
393
394
395
396
                                               and sum(restype, nuRescapable(restype, 'down', node, unit))            // downward reserves
                                             ] or
                                             [ uft_online(unit, f, t) and                                             // or the unit has an online varaible
                                                 (
397
                                                      [unit_minLoad(unit) and p_gnu(grid, node, unit, 'unitSizeGen')]      // generators with a min. load
398
399
                                                   or [p_gnu(grid, node, unit, 'maxCons')]                        // or consuming units with an online variable
                                                 )
400
                                             ] or
401
402
                                             [ gnu_input(grid, node, unit)                                        // consuming units with investment possibility
                                               and (unit_investLP(unit) or unit_investMIP(unit))
403
404
                                             ]
                                           }..
405
  + v_gen(grid, node, unit, f, t)                                                                                    // energy generation/consumption
Juha Kiviluoma's avatar
Juha Kiviluoma committed
406
  + sum( gngnu_constrainedOutputRatio(grid, node, grid_, node_, unit),
407
408
409
        p_gnu(grid_, node_, unit, 'cV') * v_gen(grid_, node_, unit, f, t) ) // considering output constraints (e.g. cV line)
  - sum(nuRescapable(restype, 'down', node, unit)$[unit_elec(unit) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')], // minus downward reserve participation
        v_reserve(restype, 'down', node, unit, f+cf_nReserves(node, restype, f, t), t) // (v_reserve can be used only if the unit is capable of providing a particular reserve)
410
    )
411
412
  =G=   // must be greater than minimum load or maximum consumption  (units with min-load and both generation and consumption are not allowed)
  // Generation units, greater than minload
413
  + v_online(unit, f+cf(f,t), t)${uft_online(unit, f, t) and p_gnu(grid, node, unit, 'unitSizeGen')}            // Online variables should only be generated for units with restrictions
414
415
416
417
      * p_gnu(grid, node, unit, 'unitSizeGen')
      * sum(effGroup, // Uses the minimum 'lb' for the current efficiency approximation
          + (p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)} + ts_effGroupUnit(effGroup, unit, 'lb', f, t))
        )
418
  + v_online_LP(unit, f+cf(f,t), t)${uft_online(unit, f, t) and p_gnu(grid, node, unit, 'unitSizeGen')}         // Online variables should only be generated for units with restrictions
419
420
421
422
423
424
425
      * (
          + p_gnu(grid, node, unit, 'unitSizeGen')
          / sum(gnu(grid_, node_, unit), p_gnu(grid_, node_, unit, 'unitSizeTot'))
        )${p_gnu(grid, node, unit, 'unitSizeGen')}
      * sum(effGroup, // Uses the minimum 'lb' for the current efficiency approximation
          + (p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)} + ts_effGroupUnit(effGroup, unit, 'lb', f, t))
        )
426
  // Consuming units, greater than maxcons
427
428
429
430
431
432
433
434
435
436
  // Capacity restriction
  + v_gen.lo(grid, node, unit, f, t)${  not uft_online(unit, f, t)
                                        and gnu_input(grid, node, unit)                                         // notice: v_gen.lo for consuming units is negative
                                        and not (unit_investLP(unit) or unit_investMIP(unit))}
  - p_gnu(grid, node, unit, 'maxCons')${  not uft_online(unit, f, t)
                                          and (unit_investLP(unit) or unit_investMIP(unit))}
  + sum(t_$(ord(t_)<=ord(t)),
      - v_invest_LP(grid, node, unit, t_)${  not uft_online(unit, f, t)                                         // notice: v_invest_LP also for consuming units is positive
                                             and p_gnu(grid, node, unit, 'maxConsCap')}
      - v_invest_MIP(unit, t_)${not uft_online(unit, f, t)}                                                     // notice: v_invest_MIP also for consuming units is positive
437
          * p_gnu(grid, node, unit, 'unitSizeCons')
438
439
440
441
442
443
444
445
446
    )
  // Online capacity restriction
  - v_online(unit, f+cf(f,t), t)${uft_online(unit, f, t) and p_gnu(grid, node, unit, 'unitSizeCons')}
      * p_gnu(grid, node, unit, 'unitSizeCons')
  - v_online_LP(unit, f+cf(f,t), t)${uft_online(unit, f, t) and p_gnu(grid, node, unit, 'unitSizeCons')}
      * (
          + p_gnu(grid, node, unit, 'unitSizeCons')
          / sum(gnu(grid_, node_, unit), p_gnu(grid_, node_, unit, 'unitSizeTot'))
        )${p_gnu(grid, node, unit, 'unitSizeCons')}
447
448
;
* -----------------------------------------------------------------------------
449
q_maxUpward(m, gnuft(grid, node, unit, f, t))${ [     ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')               // Unit is either providing
450
451
452
453
                                               and sum(restype, nuRescapable(restype, 'up', node, unit))         // upward reserves
                                             ] or
                                             [ uft_online(unit, f, t) and                                           // or the unit has an online varaible
                                                 (
454
                                                      [unit_minLoad(unit) and p_gnu(grid, node, unit, 'unitSizeCons')]  // consuming units with min_load
455
456
                                                   or [p_gnu(grid, node, unit, 'maxGen')]                          // generators with an online variable
                                                 )
457
                                             ] or
458
459
                                             [ gnu_output(grid, node, unit)                                        // generators with investment possibility
                                               and (unit_investLP(unit) or unit_investMIP(unit))
460
461
462
463
464
                                             ]
                                           }..
  + v_gen(grid, node, unit, f, t)                                                                                   // energy generation/consumption
  + sum( gngnu_constrainedOutputRatio(grid, node, grid_output, node_, unit),
         p_gnu(grid_output, node_, unit, 'cV') * v_gen(grid_output, node_, unit, f, t) )                            // considering output constraints (e.g. cV line)
465
466
  + sum(nuRescapable(restype, 'up', node, unit)$[unit_elec(unit) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')],  // plus upward reserve participation
        v_reserve(restype, 'up', node, unit, f+cf_nReserves(node, restype, f, t), t)                                // (v_reserve can be used only if the unit can provide a particular reserve)
467
    )
468
  =L=                                                                                                               // must be less than available/online capacity
469
  // Consuming units
470
  - v_online(unit, f+cf(f,t), t)${uft_online(unit, f, t) and p_gnu(grid, node, unit, 'unitSizeCons')}          // Consuming units are restricted by their min. load (consuming is negative)
471
472
473
474
      * p_gnu(grid, node, unit, 'unitSizeCons')
      * sum(effGroup, // Uses the minimum 'lb' for the current efficiency approximation
          + (p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)} + ts_effGroupUnit(effGroup, unit, 'lb', f, t))
        )
475
  - v_online_LP(unit, f+cf(f,t), t)${uft_online(unit, f, t) and p_gnu(grid, node, unit, 'unitSizeCons')}       // Consuming units are restricted by their min. load (consuming is negative)
476
477
478
479
480
481
482
      * (
          + p_gnu(grid, node, unit, 'unitSizeCons')
          / sum(gnu(grid_, node_, unit), p_gnu(grid_, node_, unit, 'unitSizeTot'))
        )${p_gnu(grid, node, unit, 'unitSizeCons')}
      * sum(effGroup, // Uses the minimum 'lb' for the current efficiency approximation
          + (p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)} + ts_effGroupUnit(effGroup, unit, 'lb', f, t))
        )
483
  // Generation units
484
  // Available capacity restrictions
485
486
487
488
489
490
491
492
493
494
  + v_gen.up(grid, node, unit, f, t)${  not uft_online(unit, f, t)                                             // Generation units are restricted by their (available) capacity
                                        and gnu_output(grid, node, unit)
                                        and not (unit_investLP(unit) or unit_investMIP(unit))}
  + {                                                                                                          // Generation units are restricted by their (available) capacity
      + p_gnu(grid, node, unit, 'maxGen')${  not uft_online(unit, f, t)
                                             and (unit_investLP(unit) or unit_investMIP(unit))}
      + sum(t_$(ord(t_)<=ord(t)),
          + v_invest_LP(grid, node, unit, t_)${  not uft_online(unit, f, t)
                                                 and p_gnu(grid, node, unit, 'maxGenCap')}
          + v_invest_MIP(unit, t_)${not uft_online(unit, f, t)}
495
              * p_gnu(grid, node, unit, 'unitSizeGen')
496
497
498
499
500
501
502
        )
    }
    * p_unit(unit, 'availability')
    * {
        + sum(flow${flowUnit(flow, unit) and unit_flow(unit)}, ts_cf_(flow, node, f, t))
        + 1${not unit_flow(unit)}
      }
503
  // Online capacity restrictions
504
  + v_online(unit, f+cf(f,t), t)${uft_online(unit, f, t) and p_gnu(grid, node, unit, 'unitSizeGen')}          // Generation units are restricted by their (online) capacity
505
506
507
508
509
510
      * p_gnu(grid, node, unit, 'unitSizeGen')
      * p_unit(unit, 'availability')
      * {
          + sum(flow${flowUnit(flow, unit) and unit_flow(unit)}, ts_cf_(flow, node, f, t))
          + 1${not unit_flow(unit)}
        }
511
  + v_online_LP(unit, f+cf(f,t), t)${uft_online(unit, f, t) and p_gnu(grid, node, unit, 'unitSizeGen')}      // Generation units are restricted by their (online) capacity
512
513
514
515
516
517
518
519
520
      * (
          + p_gnu(grid, node, unit, 'unitSizeGen')
          / sum(gnu(grid_, node_, unit), p_gnu(grid_, node_, unit, 'unitSizeTot'))
        )${p_gnu(grid, node, unit, 'unitSizeGen')}
      * p_unit(unit, 'availability')
      * {
          + sum(flow${flowUnit(flow, unit) and unit_flow(unit)}, ts_cf_(flow, node, f, t))
          + 1${not unit_flow(unit)}
        }
521
522
;
* -----------------------------------------------------------------------------
523
q_startup(uft_online(unit, f, t)) .. //${not ord(t) = sum(m, mSettings(m, 't_start'))} ..
524
  + v_online(unit, f, t)
525
  + v_online_LP(unit, f, t)
Topi Rasku's avatar
Topi Rasku committed
526
527
  =E=
  + v_online(unit, f+pf(f,t), t+pt(t)) // This reaches to tFirstSolve when pt = -1
528
  + v_online_LP(unit, f+pf(f,t), t+pt(t))
529
  + sum(starttype, v_startup(unit, starttype, f, t+pt(t)))
530
  - v_shutdown(unit, f, t+pt(t))
531
;
532
* -----------------------------------------------------------------------------
Niina Helistö's avatar
Niina Helistö committed
533
534
535
536
q_genRamp(gn(grid, node), m, s, unit, ft(f, t))${ gnuft_ramp(grid, node, unit, f, t)
*                                               and ord(t) > mSettings(m, 't_start')
                                               and ord(t) > msStart(m, s)
                                               and ord(t) <= msEnd(m, s)
537
538
539
540
541
542
543
544
545
546
547
                                               } ..
  + v_genRamp(grid, node, unit, f, t+pt(t))
  * (
      + p_stepLength(m, f+cf(f,t), t+pt(t))
        // Step length for the last time step in the previous solve
      + sum(t_${fRealization(f) and ord(t) = tSolveFirst and ord(t_) = r_realizedLast}, p_stepLengthNoReset(m, f, t_))
    )
  =E=
  // Change in generation over the time step
  + v_gen(grid, node, unit, f, t)
  - v_gen(grid, node, unit, f+cf(f,t), t+pt(t))
548
549
;
* -----------------------------------------------------------------------------
Niina Helistö's avatar
Niina Helistö committed
550
551
552
553
q_genRampChange(gn(grid, node), m, s, unit, ft(f, t))${ gnuft_ramp(grid, node, unit, f, t)
*                                                     and ord(t) > mSettings(m, 't_start')
                                                     and ord(t) > msStart(m, s)
                                                     and ord(t) <= msEnd(m, s)
554
555
556
557
                                                     and [ p_gnu(grid, node, unit, 'rampUpCost')
                                                           or p_gnu(grid, node, unit, 'rampDownCost')
                                                           ]
                                                     } ..
558
559
    + v_genRampChange(grid, node, unit, 'up', f+pf(f,t), t+pt(t))
    - v_genRampChange(grid, node, unit, 'down', f+pf(f,t), t+pt(t))
560
    =E=
561
    + v_genRamp(grid, node, unit, f, t)
562
    - v_genRamp(grid, node, unit, f+pf(f,t), t+pt(t));
563
* -----------------------------------------------------------------------------
564
q_conversionDirectInputOutput(suft(effDirect, unit, f, t)) ..
Juha Kiviluoma's avatar
Juha Kiviluoma committed
565
566
567
568
569
570
  - sum(gnu_input(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
    )
  + sum(uFuel(unit, 'main', fuel),
      + v_fuelUse(fuel, unit, f, t)
    )
571
  =E=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
572
573
  + sum(gnu_output(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
574
          * (p_effUnit(effDirect, unit, effDirect, 'slope')${not ts_effUnit(effDirect, unit, effDirect, 'slope', f, t)} + ts_effUnit(effDirect, unit, effDirect, 'slope', f, t))
575
    )
576
  + v_online(unit, f+cf(f,t), t)${uft_online(unit, f, t)}
577
      * sum( gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSizeGen') )
578
      * (p_effGroupUnit(effDirect, unit, 'section')${not ts_effUnit(effDirect, unit, effDirect, 'section', f, t)} + ts_effUnit(effDirect, unit, effDirect, 'section', f, t))
579
;
Juha Kiviluoma's avatar
Juha Kiviluoma committed
580
581
582
583
584
585
586
587
588
* -----------------------------------------------------------------------------
q_conversionSOS2InputIntermediate(suft(effGroup, unit, f, t))$effLambda(effGroup) ..
  - sum(gnu_input(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
    )
  + sum(uFuel(unit, 'main', fuel),
      + v_fuelUse(fuel, unit, f, t)
    )
  =E=
589
590
591
592
593
  + ( + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
          + v_sos2(unit, f, t, effSelector)
              * ( p_effUnit(effGroup, unit, effSelector, 'op')${not ts_effUnit(effGroup, unit, effSelector, 'op', f, t)} + ts_effUnit(effGroup, unit, effSelector, 'op', f, t))
              * ( p_effUnit(effGroup, unit, effSelector, 'slope')${not ts_effUnit(effGroup, unit, effSelector, 'slope', f, t)} + ts_effUnit(effGroup, unit, effSelector, 'slope', f, t) )
        )
594
      + v_online(unit, f+cf(f,t), t)
595
          * p_effGroupUnit(effGroup, unit, 'section')
Juha Kiviluoma's avatar
Juha Kiviluoma committed
596
    )
597
      * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSizeGen'))
598
;
599
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
600
q_conversionSOS2Constraint(suft(effGroup, unit, f, t))$effLambda(effGroup) ..
601
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
602
603
      + v_sos2(unit, f, t, effSelector)
    )
604
  =E=
605
  + v_online(unit, f+cf(f,t), t)${uft_online(unit, f, t)}
606
*  + 1${not uft_online(unit, f, t)} // Should not be required, as effLambda implies online variables
607
;
608
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
609
q_conversionSOS2IntermediateOutput(suft(effGroup, unit, f, t))$effLambda(effGroup) ..
610
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
611
      + v_sos2(unit, f, t, effSelector)
612
      * (p_effUnit(effGroup, unit, effSelector, 'op')${not ts_effUnit(effGroup, unit, effSelector, 'op', f, t)} + ts_effUnit(effGroup, unit, effSelector, 'op', f, t))
Juha Kiviluoma's avatar
Juha Kiviluoma committed
613
    )
614
  * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSizeGen'))
615
  =E=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
616
617
618
  + sum(gnu_output(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
    )
619
;
620
* -----------------------------------------------------------------------------
621
q_outputRatioFixed(gngnu_fixedOutputRatio(grid, node, grid_, node_, unit), ft(f, t))${uft(unit, f, t)} ..
622
  + v_gen(grid, node, unit, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
623
624
      / p_gnu(grid, node, unit, 'cB')
  =E=
625
  + v_gen(grid_, node_, unit, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
626
627
628
      / p_gnu(grid_, node_, unit, 'cB')
;
* -----------------------------------------------------------------------------
629
630
q_outputRatioConstrained(gngnu_constrainedOutputRatio(grid, node, grid_, node_, unit), ft(f, t))${uft(unit, f, t)} ..
  + v_gen(grid, node, unit, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
631
      / p_gnu(grid, node, unit, 'cB')
632
  =G=
633
  + v_gen(grid_, node_, unit, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
634
      / p_gnu(grid_, node_, unit, 'cB')
635
;
636
* -----------------------------------------------------------------------------
637
638
639
q_stateSlack(gn_stateSlack(grid, node), slack, ft_full(f, t))${ p_gnBoundaryPropertiesForStates(grid, node, slack, 'slackCost')
                                                                and [ft_dynamic(f,t) or sum(m, mftStart(m, f, t))]
                                                                } ..
640
  + v_stateSlack(grid, node, slack, f, t)
641
  =G=
642
  + p_slackDirection(slack) * (
643
      + v_state(grid, node, f+cf_Central(f,t), t)
644
645
646
      - p_gnBoundaryPropertiesForStates(grid, node, slack, 'constant')$p_gnBoundaryPropertiesForStates(grid, node, slack, 'useConstant')
      - ts_nodeState(grid, node, slack, f, t)$p_gnBoundaryPropertiesForStates(grid, node, slack, 'useTimeSeries')
    )
647
;
Juha Kiviluoma's avatar
Cleanup    
Juha Kiviluoma committed
648
* -----------------------------------------------------------------------------
649
650
q_stateUpwardLimit(gn_state(grid, node), m, ft_dynamic(f, t))${ sum(gn2gnu(grid, node, grid_, node_output, unit)$(sum(restype, nuRescapable(restype, 'down', node_output, unit))), 1)  // nodes that have units with endogenous output with possible reserve provision
                                                                or sum(gn2gnu(grid_, node_input, grid, node, unit)$(sum(restype, nuRescapable(restype, 'down', node_input , unit))), 1)  // or nodes that have units with endogenous input with possible reserve provision
651
                                                                or sum(gnu(grid, node, unit), p_gnu(grid, node, unit, 'upperLimitCapacityRatio'))  // or nodes that have units whose invested capacity limits their state
652
                                                                }..
653
  ( // Utilizable headroom in the state variable
654
      + p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useConstant') * p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'constant')
655
      + p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useTimeSeries') * ts_nodeState(grid, node, 'upwardLimit', f, t)
656
      + sum{gnu(grid, node, unit), sum(t_$(ord(t_)<=ord(t)), v_invest_LP(grid, node, unit, t_) * p_gnu(grid, node, unit, 'upperLimitCapacityRatio'))}
657
658
659
660
661
662
663
      + sum{gnu(grid, node, unit),
          + sum(t_$(ord(t_)<=ord(t)),
              + v_invest_MIP(unit, t_)
              * p_gnu(grid, node, unit, 'unitSizeTot')
              * p_gnu(grid, node, unit, 'upperLimitCapacityRatio')
            )
        }
664
      - v_state(grid, node, f+cf_Central(f,t), t)
665
666
667
668
669
670
671
672
  )
      * ( // Accounting for the energyStoredPerUnitOfState ...
          + p_gn(grid, node, 'energyStoredPerUnitOfState')
          + p_stepLength(m , f+pf(f,t), t+pt(t))
              * ( // ... and the change in energy losses from the node
                  + p_gn(grid, node, 'selfDischargeLoss')
                  + sum(to_node, p_gnn(grid, node, to_node, 'diffCoeff'))
                )
673
        )
674
675
676
  =G=
  + p_stepLength(m, f+pf(f,t), t+pt(t))
      * ( // Reserve provision from units that have output to this node
677
          + sum(gn2gnu(grid_, node_input, grid, node, unit)${uft(unit, f, t+pt(t))},
678
              + sum(restype$[nuRescapable(restype, 'down', node_input, unit) and unit_elec(unit) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')], // Downward reserves from units that output energy to the node
679
                  + v_reserve(restype, 'down', node_input, unit, f+pf_nReserves(node_input, restype, f, t), t+pt(t))
680
                      / sum(effGroup${suft(effGroup, unit, f, t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
681
682
683
                )
            )
          // Reserve provision from units that take input from this node
684
          + sum(gn2gnu(grid, node, grid_, node_output, unit)${uft(unit, f, t+pt(t))},
685
              + sum(restype$[nuRescapable(restype, 'down', node_output, unit) and unit_elec(unit) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')], // Downward reserves from units that use the node as energy input
686
                  + v_reserve(restype, 'down', node_output, unit, f+pf_nReserves(node_output, restype, f, t), t+pt(t))
687
                      * sum(effGroup${suft(effGroup, unit, f, t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
688
                )
689
            )
690
691
      // Here we could have a term for using the energy in the node to offer reserves as well as imports and exports of reserves, but as long as reserves are only
      // considered in power grids that do not have state variables, these terms are not needed. Earlier commit (29.11.2016) contains a draft of those terms.
692
        )
693
;
Juha Kiviluoma's avatar
Cleanup    
Juha Kiviluoma committed
694
* -----------------------------------------------------------------------------
695
696
697
q_stateDownwardLimit(gn_state(grid, node), m, ft_dynamic(f, t))${   sum(gn2gnu(grid, node, grid_, node_output, unit)$(sum(restype, nuRescapable(restype, 'up', node_output, unit))), 1)  // nodes that have units with endogenous output with possible reserve provision
                                                                    or sum(gn2gnu(grid_, node_input, grid, node, unit) $(sum(restype, nuRescapable(restype, 'up', node_input , unit))), 1)  // or nodes that have units with endogenous input with possible reserve provision
                                                                    } ..
698
  ( // Utilizable headroom in the state variable
699
      + v_state(grid, node, f+cf_Central(f,t), t)
700
701
702
703
704
705
706
707
708
709
      - p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')   * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'constant')
      - p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useTimeSeries') * ts_nodeState(grid, node, 'downwardLimit', f, t)
  )
      * ( // Accounting for the energyStoredPerUnitOfState ...
          + p_gn(grid, node, 'energyStoredPerUnitOfState')
          + p_stepLength(m , f+pf(f,t), t+pt(t))
              * ( // ... and the change in energy losses from the node
                  + p_gn(grid, node, 'selfDischargeLoss')
                  + sum(to_node, p_gnn(grid, node, to_node, 'diffCoeff'))
                )
710
        )
711
712
713
  =G=
  + p_stepLength(m, f+pf(f,t), t+pt(t))
      * ( // Reserve provision from units that have output to this node
714
          + sum(gn2gnu(grid_, node_input, grid, node, unit)${uft(unit, f, t+pt(t))},
715
              + sum(restype$[nuRescapable(restype, 'up', node_input, unit) and unit_elec(unit) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')], // Upward reserves from units that output energy to the node
716
                  + v_reserve(restype, 'up', node_input, unit, f+pf_nReserves(node_input, restype, f, t), t+pt(t))
717
                      / sum(effGroup${suft(effGroup, unit, f, t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
718
719
720
                )
            )
          // Reserve provision from units that take input from this node
721
          + sum(gn2gnu(grid, node, grid_, node_output, unit)${uft(unit, f, t+pt(t))},
722
              + sum(restype$[nuRescapable(restype, 'up', node_output, unit) and unit_elec(unit) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')], // Upward reserves from units that use the node as energy input
723
                  + v_reserve(restype, 'up', node_output, unit, f+pf_nReserves(node_output, restype, f, t), t+pt(t))
724
                      * sum(effGroup${suft(effGroup, unit, f, t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
725
                )
726
727
728
            )
      // Here we could have a term for using the energy in the node to offer reserves as well as imports and exports of reserves, but as long as reserves are only
      // considered in power grids that do not have state variables, these terms are not needed. Earlier commit (29.11.2016) contains a draft of those terms.
729
        )
730
731
;
* -----------------------------------------------------------------------------
732
q_boundState(gnn_boundState(grid, node, node_), m, ft_dynamic(f, t)) ..
733
    + v_state(grid, node, f+cf_Central(f,t), t)   // The state of the first node sets the upper limit of the second
734
    + ( // Downward reserve provided by units in node
735
736
*        - sum(nuRescapable(restype, 'down', node, unit)${nuft(unit, f+pf(f,t), t+pt(t))},
*            + v_reserve(restype, 'down', node, unit, f+pf(f,t), t+pt(t))
737
738
*          )
        // Upwards reserve provided by input units
739
        - sum(nuRescapable(restype, 'up', node_input, unit)${sum(grid_, gn2gnu(grid_, node_input, grid, node, unit)) AND uft(unit, f, t+pt(t)) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')},
740
            + v_reserve(restype, 'up', node_input, unit, f+pf_nReserves(node_input, restype, f, t), t+pt(t))
741
                / sum(effGroup${suft(effGroup, unit, f, t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
742
          )
743
        // Upwards reserve providewd by output units
744
        - sum(nuRescapable(restype, 'up', node_output, unit)${sum(grid_, gn2gnu(grid, node, grid_, node_output, unit)) AND uft(unit, f, t+pt(t)) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')},
745
            + v_reserve(restype, 'up', node_output, unit, f+pf_nReserves(node_output, restype, f, t), t+pt(t))
746
                / sum(effGroup${suft(effGroup, unit, f, t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
747
          )
748
749
        // Here we could have a term for using the energy in the node to offer reserves as well as imports and exports of reserves, but as long as reserves are only
        // considered in power grids that do not have state variables, these terms are not needed. Earlier commit (16.2.2017) contains a draft of those terms.
750
      )
751
        / (p_gn(grid, node, 'energyStoredPerUnitOfState') )                                     // Divide by the stored energy in the node per unit of v_state to obtain same unit as the v_state
752
753
        * p_stepLength(m, f+pf(f,t), t+pt(t))                                                   // Multiply with time step to obtain change in state over the step
    =G=
754
    + v_state(grid, node_, f+cf_Central(f,t), t)
755
    + p_gnn(grid, node, node_, 'boundStateOffset')                                              // Affected by the offset parameter
756
    + (  // Possible reserve by this node
757
758
*        + sum(nuRescapable(restype, 'up', node_, unit)${nuft(node, unit, f+pf(f,t), t+pt(t))},
*            + v_reserve(restype, 'up', node_, unit, f+pf(f,t), t+pt(t))
759
760
*          )
        // Possible reserve by input node
761
        + sum(nuRescapable(restype, 'down', node_input, unit)${sum(grid_, gn2gnu(grid_, node_input, grid, node_, unit)) AND uft(unit, f, t+pt(t)) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')},
762
            + v_reserve(restype, 'down', node_input, unit, f+pf_nReserves(node_input, restype, f, t), t+pt(t))               // NOTE! If elec-elec conversion, this might result in weird reserve requirements!
763
                / sum(effGroup${suft(effGroup, unit, f, t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
764
          )
765
        // Possible reserve by output node
766
        + sum(nuRescapable(restype, 'down', node_output, unit)${sum(grid_, gn2gnu(grid, node_, grid_, node_output, unit)) AND uft(unit, f, t+pt(t)) and ord(t) < tSolveFirst + mSettings(m, 't_reserveLength')},
767
            + v_reserve(restype, 'down', node_output, unit, f+pf_nReserves(node_output, restype, f, t), t+pt(t))               // NOTE! If elec-elec conversion, this might result in weird reserve requirements!
768
                / sum(effGroup${suft(effGroup, unit, f, t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f, t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
769
          )
770
771
        // Here we could have a term for using the energy in the node to offer reserves as well as imports and exports of reserves, but as long as reserves are only
        // considered in power grids that do not have state variables, these terms are not needed. Earlier commit (16.2.2017) contains a draft of those terms.
772
      )
773
        / (p_gn(grid, node_, 'energyStoredPerUnitOfState') )                                    // Divide by the stored energy in the node per unit of v_state to obtain same unit as the v_state
774
        * p_stepLength(m, f+pf(f,t), t+pt(t))                                                   // Multiply with time step to obtain change in state over the step
775
;
776
* -----------------------------------------------------------------------------
777
q_boundStateMaxDiff(gnn_state(grid, node, node_), m, ft_dynamic(f, t))$p_gnn(grid, node, node_, 'boundStateMaxDiff') ..
778
  + v_state(grid, node, f+cf_Central(f,t), t)  // The state of the first node sets the lower limit of the second
779
  =L=
780
781
  + v_state(grid, node_, f+cf_Central(f,t), t)
  + p_gnn(grid, node, node_, 'boundStateMaxDiff')  // Affected by the maximum difference parameter
782
783
;
* -----------------------------------------------------------------------------
784
785
786
787
q_boundCyclic(gn_state(grid, node), mftStart(m, f, t), fCentral(f_), t_)${  p_gn(grid, node, 'boundCyclic')         // Bind variables if parameter found
                                                                            AND tSolveFirst = mSettings(m, 't_start') // For the very first model solve only
                                                                            AND mftLastSteps(m, f_, t_)              // Use only the ending time step of the model solve
                                                                            }..
788
    + v_state(grid, node, f+cf_Central(f,t), t)
789
    =E=
790
    + v_state(grid, node, f_+cf_Central(f,t), t_)
791
;
792
*-----------------------------------------------------------------------------
793
q_fixedGenCap1U(gnu(grid, node, unit), t_invest(t))${unit_investLP(unit)} ..
794
795
796
797
798
  + v_invest_LP(grid, node, unit, t)
  =E=
  + sum((grid_, node_), v_invest_LP(grid_, node_, unit, t))
  * p_gnu(grid, node, unit, 'unitSizeTot')
  / sum((grid_, node_), p_gnu(grid_, node_, unit, 'unitSizeTot'))
799
800
801
;
*-----------------------------------------------------------------------------
q_fixedGenCap2U(grid, node, unit, grid_, node_, unit_, t_invest(t))${p_gnugnu(grid, node, unit, grid_, node_, unit_, 'capacityRatio')} ..
802
803
804
805
806
807
808
809
  + v_invest_LP(grid, node, unit, t)
  + v_invest_MIP(unit, t)
  =E=
  (
    + v_invest_LP(grid_, node_, unit_, t)
    + v_invest_MIP(unit_, t)
  )
  * p_gnugnu(grid, node, unit, grid_, node_, unit_, 'capacityRatio')
810
811
;
*-----------------------------------------------------------------------------
812
q_onlineLimit(uft_online(unit, ft(f,t)))${unit_investMIP(unit) or unit_investLP(unit)} ..
813
814
815
816
817
818
819
820
821
  + v_online(unit, f, t)
  + v_online_LP(unit, f, t)
  =L=
  + p_unit(unit, 'unitCount')${unit_investMIP(unit)}  // Number of existing units
  + sum(gnu(grid, node, unit)${unit_investLP(unit)}, p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons'))  // Capacity of existing units
  + sum(t_$(ord(t_)<=ord(t)),
      + v_invest_MIP(unit, t_)  // Number of invested units
      + sum(gnu(grid, node, unit), v_invest_LP(grid, node, unit, t_))  // Capacity of invested units
    )
822
;
823
* -----------------------------------------------------------------------------
Niina Helistö's avatar
Niina Helistö committed
824
825
q_rampUpLimit(gn(grid, node), m, s, unit, ft(f, t))${ gnuft_ramp(grid, node, unit, f, t)
                                                   and ord(t) > msStart(m, s)
826
                                                   and msft(m, s, f, t)
Niina Helistö's avatar
Niina Helistö committed
827
                                                   and p_gnu(grid, node, unit, 'maxRampUp')
828
829
830
                                                   and (uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))
                                                           or unit_investLP(unit)
                                                           or unit_investMIP(unit))
Niina Helistö's avatar
Niina Helistö committed
831
                                                   } ..
832
833
  + v_genRamp(grid, node, unit, f, t+pt(t))
  =L=
Niina Helistö's avatar
Niina Helistö committed
834
    // Ramping capability of units without online variable
835
  + (
Niina Helistö's avatar
Niina Helistö committed
836
      + ( p_gnu(grid, node, unit, 'maxGen') - p_gnu(grid, node, unit, 'maxCons') )${not uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
837
      + sum(t_$(ord(t_)<=ord(t)),
Niina Helistö's avatar
Niina Helistö committed
838
839
840
          + v_invest_LP(grid, node, unit, t_)${not uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t)) and p_gnu(grid, node, unit, 'maxGenCap')}
          - v_invest_LP(grid, node, unit, t_)${not uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t)) and p_gnu(grid, node, unit, 'maxConsCap')}
          + v_invest_MIP(unit, t_)${not uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
841
              * p_gnu(grid, node, unit, 'unitSizeGenNet')
842
        )
843
    )
Niina Helistö's avatar
Niina Helistö committed
844
845
846
847
848
849
850
851
852
      * p_gnu(grid, node, unit, 'maxRampUp')
      * 60 / 100  // Unit conversion from [p.u./min] to [MW/h]
    // Ramping capability of units that were online both in the previous time step and the current time step
  + (
      + v_online_LP(unit, f+cpf(f,t), t+pt(t))${uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
      + v_online(unit, f+cpf(f,t), t+pt(t))${uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
      - v_shutdown(unit, f+cf(f,t), t+pt(t))${uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
    )
      * p_gnu(grid, node, unit, 'unitSizeGenNet')
853
      / {
854
          + 1${  not unit_investLP(unit)
855
                 or not p_gnu(grid, node, unit, 'unitSizeGenNet')
856
              }
857
858
859
860
          + sum(gnu(grid_, node_, unit)${ unit_investLP(unit)
                                          and p_gnu(grid, node, unit, 'unitSizeGenNet')
                }, p_gnu(grid_, node_, unit, 'unitSizeTot')
            )
861
        } // Scaling factor to calculate online capacity in gn(grid, node) in the case of continuous investments
862
863
      * p_gnu(grid, node, unit, 'maxRampUp')
      * 60 / 100  // Unit conversion from [p.u./min] to [MW/h]
864
865
866
  // Newly started units are assumed to start to their minload and
  // newly shutdown units are assumed to be shut down from their minload.
  + (
Niina Helistö's avatar
Niina Helistö committed
867
868
869
      + sum(starttype, v_startup(unit, starttype, f+cf(f,t), t+pt(t)))
      - v_shutdown(unit, f+cf(f,t), t+pt(t))
    )${uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
870
      * p_gnu(grid, node, unit, 'unitSizeGenNet')
871
      / {
872
873
874
875
876
          + 1${not unit_investLP(unit) or not p_gnu(grid, node, unit, 'unitSizeGenNet')}
          + sum(gnu(grid_, node_, unit)${ unit_investLP(unit)
                                          and p_gnu(grid, node, unit, 'unitSizeGenNet')
                }, p_gnu(grid_, node_, unit, 'unitSizeTot')
            )
877
        } // Scaling factor to calculate online capacity in gn(grid, node) in the case of continuous investments
878
      * sum(suft(effGroup, unit, f+cf(f,t), t), p_effGroupUnit(effGroup, unit, 'lb'))
879
// Reserve provision?
880
881
882
883
// Note: This constraint does not limit ramping properly for example if online subunits are
// producing at full capacity (= not possible to ramp up) and more subunits are started up.
// Take this into account in q_maxUpward or in another equation?:
// v_gen =L= (v_online(t-1) - v_shutdown(t-1)) * unitSize + v_startup(t-1) * unitSize * minLoad
884
;
885
* -----------------------------------------------------------------------------
Niina Helistö's avatar
Niina Helistö committed
886
887
q_rampDownLimit(gn(grid, node), m, s, unit, ft(f, t))${ gnuft_ramp(grid, node, unit, f, t)
                                                     and ord(t) > msStart(m, s)
888
                                                     and msft(m, s, f, t)
Niina Helistö's avatar
Niina Helistö committed
889
                                                     and p_gnu(grid, node, unit, 'maxRampDown')
890
891
892
                                                     and (uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))
                                                             or unit_investLP(unit)
                                                             or unit_investMIP(unit))
Niina Helistö's avatar
Niina Helistö committed
893
                                                     } ..
894
895
  + v_genRamp(grid, node, unit, f, t+pt(t))
  =G=
Niina Helistö's avatar
Niina Helistö committed
896
    // Ramping capability of units without online variable
897
  - (
Niina Helistö's avatar
Niina Helistö committed
898
      + ( p_gnu(grid, node, unit, 'maxGen') - p_gnu(grid, node, unit, 'maxCons') )${not uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
899
      + sum(t_$(ord(t_)<=ord(t)),
Niina Helistö's avatar
Niina Helistö committed
900
901
902
          + v_invest_LP(grid, node, unit, t_)${not uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t)) and p_gnu(grid, node, unit, 'maxGenCap')}
          - v_invest_LP(grid, node, unit, t_)${not uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t)) and p_gnu(grid, node, unit, 'maxConsCap')}
          + v_invest_MIP(unit, t_)${not uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
903
              * p_gnu(grid, node, unit, 'unitSizeGenNet')
904
        )
905
    )
Niina Helistö's avatar
Niina Helistö committed
906
907
908
909
910
911
912
913
914
      * p_gnu(grid, node, unit, 'maxRampDown')
      * 60 / 100  // Unit conversion from [p.u./min] to [MW/h]
    // Ramping capability of units that were online both in the previous time step and the current time step
  - (
      + v_online_LP(unit, f+cpf(f,t), t+pt(t))${uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
      + v_online(unit, f+cpf(f,t), t+pt(t))${uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
      - v_shutdown(unit, f+cf(f,t), t+pt(t))${uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
    )
      * p_gnu(grid, node, unit, 'unitSizeGenNet')
915
      / {
916
          + 1${  not unit_investLP(unit)
917
                 or not p_gnu(grid, node, unit, 'unitSizeGenNet')
918
              }
919
920
921
922
          + sum(gnu(grid_, node_, unit)${ unit_investLP(unit)
                                          and p_gnu(grid, node, unit, 'unitSizeGenNet')
                }, p_gnu(grid_, node_, unit, 'unitSizeTot')
            )
923
        } // Scaling factor to calculate online capacity in gn(grid, node) in the case of continuous investments
924
925
      * p_gnu(grid, node, unit, 'maxRampDown')
      * 60 / 100  // Unit conversion from [p.u./min] to [MW/h]
926
927
928
  // Newly started units are assumed to start to their minload and
  // newly shutdown units are assumed to be shut down from their minload.
  + (
Niina Helistö's avatar
Niina Helistö committed
929
930
931
      + sum(starttype, v_startup(unit, starttype, f+cf(f,t), t+pt(t)))
      - v_shutdown(unit, f+cf(f,t), t+pt(t))
    )${uft_online_incl_previous(unit, f+cpf(f,t), t+pt(t))}
932
      * p_gnu(grid, node, unit, 'unitSizeGenNet')
933
      / {
934
935
936
937
938
          + 1${not unit_investLP(unit) or not p_gnu(grid, node, unit, 'unitSizeGenNet')}
          + sum(gnu(grid_, node_, unit)${ unit_investLP(unit)
                                          and p_gnu(grid, node, unit, 'unitSizeGenNet')
                }, p_gnu(grid_, node_, unit, 'unitSizeTot')
            )
939
        } // Scaling factor to calculate online capacity in gn(grid, node) in the case of continuous investments
940
      * sum(suft(effGroup, unit, f+cf(f,t), t), p_effGroupUnit(effGroup, unit, 'lb'))
941
942
943
944
// Reserve provision?
;
*-----------------------------------------------------------------------------
q_startuptype(m, unit, starttype, f, t)${  uft_online(unit, f, t)
945
                                           and ft(f,t)
946
947
948
949
950
                                           and starttypeConstrained(starttype)} ..
  + v_startup(unit, starttype, f, t)
  =L=
  + sum(t_${  ord(t_)>[ord(t)-p_uNonoperational(unit, starttype, 'max') / mSettings(m, 'intervalInHours')]
              and ord(t_)<=[ord(t)-p_uNonoperational(unit, starttype, 'min') / mSettings(m, 'intervalInHours')]
951
952
953
              and p_stepLengthNoReset(m, f+af(f,t_), t_)
        },
          + v_shutdown(unit, f+af(f,t_), t_)
954
    )
955
956
957
958
* How to take into account varying time step lengths? And forecasts?
;
*-----------------------------------------------------------------------------
q_minUp(m, unit, f, t)${uft_online(unit, f, t) and ft_dynamic(f,t) and not unit_investLP(unit)} ..
959
960
  + sum(t_${  ord(t_)>=[ord(t)-p_unit(unit, 'minOperationTime') / mSettings(m, 'intervalInHours')]
              and ord(t_)<ord(t)
961
962
              and p_stepLengthNoReset(m, f+af(f,t_), t_)
        }, sum(starttype, v_startup(unit, starttype, f+af(f,t_), t_))
963
964
965
    )
  =L=
  + v_online(unit, f, t)
966
967
968
969
* How to take into account varying time step lengths? And forecasts?
;
*-----------------------------------------------------------------------------
q_minDown(m, unit, f, t)${uft_online(unit, f, t) and ft_dynamic(f,t) and not unit_investLP(unit)} ..
970
971
  + sum(t_${  ord(t_)>=[ord(t)-p_unit(unit, 'minShutDownTime') / mSettings(m, 'intervalInHours')]
              and ord(t_)<ord(t)
972
973
              and p_stepLengthNoReset(m, f+af(f,t_), t_)
        }, v_shutdown(unit, f+af(f,t_), t_)
974
975
976
977
978
    )
  =L=
  + p_unit(unit, 'unitCount')
  + sum(t__$(ord(t__)<=ord(t)), v_invest_MIP(unit, t__))
  - v_online(unit, f, t)
979
* How to take into account varying time step lengths? And forecasts?
980
981
;
*-----------------------------------------------------------------------------
982
q_capacityMargin(gn(grid, node), ft(f, t))${p_gn(grid, node, 'capacityMargin')} ..
983
  + sum(gnu_output(grid, node, unit)${  not (unit_investLP(unit) or unit_investMIP(unit))
984
                                        and not sum(gn2gnu(grid_, node_input, grid, node, unit), 1)
985
986
        }, v_gen.up(grid, node, unit, f, t)
    )
987
  + sum(gnu_output(grid, node, unit)${  (unit_investLP(unit) or unit_investMIP(unit))
988
                                        and not sum(gn2gnu(grid_, node_input, grid, node, unit), 1)
989
990
991
992
993
994
995
996
997
998
        },
      + p_unit(unit, 'availability')
      * sum(t_$(ord(t_)<=ord(t)),
          + v_invest_LP(grid, node, unit, t_)${not unit_flow(unit)}
          + v_inves